summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Android.bp4
-rw-r--r--OWNERS1
-rw-r--r--StubLibraries.bp8
-rw-r--r--apct-tests/perftests/windowmanager/AndroidManifest.xml7
-rw-r--r--apct-tests/perftests/windowmanager/src/android/wm/InTaskTransitionTest.java140
-rw-r--r--apct-tests/perftests/windowmanager/src/android/wm/WindowManagerPerfTestBase.java46
-rw-r--r--apex/jobscheduler/framework/java/android/app/AlarmManager.java4
-rw-r--r--apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java46
-rw-r--r--apex/jobscheduler/framework/java/android/app/job/IJobCallback.aidl14
-rw-r--r--apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl8
-rw-r--r--apex/jobscheduler/framework/java/android/app/job/IUserVisibleJobObserver.aidl33
-rw-r--r--apex/jobscheduler/framework/java/android/app/job/JobParameters.java10
-rw-r--r--apex/jobscheduler/framework/java/android/app/job/JobScheduler.java169
-rw-r--r--apex/jobscheduler/framework/java/android/app/job/JobService.java86
-rw-r--r--apex/jobscheduler/framework/java/android/app/job/JobServiceEngine.java46
-rw-r--r--apex/jobscheduler/framework/java/android/app/job/UserVisibleJobSummary.aidl (renamed from core/java/android/view/InsetsVisibilities.aidl)8
-rw-r--r--apex/jobscheduler/framework/java/android/app/job/UserVisibleJobSummary.java122
-rw-r--r--apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java9
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java21
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java514
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java75
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobStore.java60
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java63
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java141
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/restrictions/JobRestriction.java25
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java2
-rw-r--r--api/Android.bp2
-rw-r--r--api/api.go31
-rw-r--r--boot/preloaded-classes4
-rw-r--r--cmds/bootanimation/BootAnimation.cpp5
-rw-r--r--config/preloaded-classes4
-rw-r--r--core/api/current.txt136
-rw-r--r--core/api/module-lib-current.txt23
-rw-r--r--core/api/system-current.txt226
-rw-r--r--core/api/test-current.txt47
-rw-r--r--core/java/Android.bp15
-rw-r--r--core/java/android/accessibilityservice/AccessibilityService.java54
-rw-r--r--core/java/android/accessibilityservice/AccessibilityServiceInfo.java47
-rw-r--r--core/java/android/app/ActivityManagerInternal.java5
-rw-r--r--core/java/android/app/ActivityThread.java45
-rw-r--r--core/java/android/app/ForegroundServiceTypePolicy.java19
-rw-r--r--core/java/android/app/GameManager.java1
-rw-r--r--core/java/android/app/GameModeConfiguration.java18
-rw-r--r--core/java/android/app/GameModeInfo.java34
-rw-r--r--core/java/android/app/IActivityManager.aidl3
-rw-r--r--core/java/android/app/IApplicationThread.aidl4
-rw-r--r--core/java/android/app/IUiAutomationConnection.aidl1
-rw-r--r--core/java/android/app/ReceiverInfo.aidl60
-rw-r--r--core/java/android/app/ResourcesManager.java109
-rw-r--r--core/java/android/app/Service.java19
-rw-r--r--core/java/android/app/StatusBarManager.java5
-rw-r--r--core/java/android/app/SystemServiceRegistry.java13
-rw-r--r--core/java/android/app/UiAutomation.java18
-rw-r--r--core/java/android/app/UiAutomationConnection.java5
-rw-r--r--core/java/android/app/WallpaperManager.java82
-rw-r--r--core/java/android/app/admin/DevicePolicyCache.java1
-rw-r--r--core/java/android/app/admin/DevicePolicyManager.java10
-rw-r--r--core/java/android/app/admin/DevicePolicyManagerInternal.java10
-rw-r--r--core/java/android/app/admin/DeviceStateCache.java12
-rw-r--r--core/java/android/app/backup/BackupManager.java23
-rw-r--r--core/java/android/app/backup/BackupRestoreEventLogger.java12
-rw-r--r--core/java/android/companion/virtual/IVirtualDevice.aidl32
-rw-r--r--core/java/android/companion/virtual/IVirtualDeviceManager.aidl5
-rw-r--r--core/java/android/companion/virtual/VirtualDeviceManager.java186
-rw-r--r--core/java/android/companion/virtual/VirtualDeviceParams.java55
-rw-r--r--core/java/android/companion/virtual/sensor/VirtualSensor.java9
-rw-r--r--core/java/android/companion/virtual/sensor/VirtualSensorConfig.java10
-rw-r--r--core/java/android/companion/virtual/sensor/VirtualSensorEvent.java11
-rw-r--r--core/java/android/content/Context.java8
-rw-r--r--core/java/android/content/Intent.java23
-rw-r--r--core/java/android/content/om/OverlayInfo.java9
-rw-r--r--core/java/android/content/om/TEST_MAPPING6
-rw-r--r--core/java/android/content/pm/ActivityInfo.java29
-rw-r--r--core/java/android/content/pm/PackageInstaller.java11
-rw-r--r--core/java/android/content/pm/PackageManager.java5
-rw-r--r--core/java/android/content/pm/PermissionInfo.java14
-rw-r--r--core/java/android/content/pm/ServiceInfo.java25
-rw-r--r--core/java/android/content/pm/TEST_MAPPING30
-rw-r--r--core/java/android/credentials/ClearCredentialStateException.java76
-rw-r--r--core/java/android/credentials/CreateCredentialException.java77
-rw-r--r--core/java/android/credentials/CreateCredentialRequest.java2
-rw-r--r--core/java/android/credentials/CredentialManager.java30
-rw-r--r--core/java/android/credentials/CredentialManagerException.java62
-rw-r--r--core/java/android/credentials/GetCredentialException.java77
-rw-r--r--core/java/android/credentials/GetCredentialOption.java87
-rw-r--r--core/java/android/credentials/IClearCredentialStateCallback.aidl2
-rw-r--r--core/java/android/credentials/ICreateCredentialCallback.aidl2
-rw-r--r--core/java/android/credentials/IGetCredentialCallback.aidl2
-rw-r--r--core/java/android/hardware/SystemSensorManager.java20
-rw-r--r--core/java/android/hardware/camera2/CameraCharacteristics.java3
-rw-r--r--core/java/android/hardware/camera2/CameraMetadata.java2
-rw-r--r--core/java/android/hardware/camera2/CaptureRequest.java34
-rw-r--r--core/java/android/hardware/camera2/CaptureResult.java58
-rw-r--r--core/java/android/hardware/display/AmbientDisplayConfiguration.java6
-rw-r--r--core/java/android/hardware/display/DisplayManager.java9
-rw-r--r--core/java/android/hardware/display/DisplayManagerInternal.java8
-rw-r--r--core/java/android/hardware/fingerprint/FingerprintManager.java36
-rw-r--r--core/java/android/hardware/input/IInputManager.aidl32
-rw-r--r--core/java/android/hardware/input/InputManager.java168
-rw-r--r--core/java/android/hardware/input/VirtualDpad.java16
-rw-r--r--core/java/android/hardware/input/VirtualDpadConfig.aidl19
-rw-r--r--core/java/android/hardware/input/VirtualDpadConfig.java75
-rw-r--r--core/java/android/hardware/input/VirtualInputDeviceConfig.java144
-rw-r--r--core/java/android/hardware/input/VirtualKeyboardConfig.aidl19
-rw-r--r--core/java/android/hardware/input/VirtualKeyboardConfig.java77
-rw-r--r--core/java/android/hardware/input/VirtualMouseConfig.aidl19
-rw-r--r--core/java/android/hardware/input/VirtualMouseConfig.java75
-rw-r--r--core/java/android/hardware/input/VirtualTouchscreenConfig.aidl19
-rw-r--r--core/java/android/hardware/input/VirtualTouchscreenConfig.java118
-rw-r--r--core/java/android/hardware/location/ContextHubManager.java29
-rw-r--r--core/java/android/hardware/location/IContextHubService.aidl4
-rw-r--r--core/java/android/nfc/AvailableNfcAntenna.java20
-rw-r--r--core/java/android/os/Binder.java20
-rwxr-xr-xcore/java/android/os/Build.java22
-rw-r--r--core/java/android/os/IUserManager.aidl1
-rw-r--r--core/java/android/os/OWNERS3
-rw-r--r--core/java/android/os/UserManager.java61
-rw-r--r--core/java/android/provider/DeviceConfig.java1568
-rw-r--r--core/java/android/provider/Settings.java111
-rw-r--r--core/java/android/security/keymaster/KeymasterDefs.java2
-rw-r--r--core/java/android/security/rkp/IGetKeyCallback.aidl49
-rw-r--r--core/java/android/security/rkp/IGetRegistrationCallback.aidl49
-rw-r--r--core/java/android/security/rkp/IRegistration.aidl85
-rw-r--r--core/java/android/security/rkp/IRemoteProvisioning.aidl68
-rw-r--r--core/java/android/security/rkp/OWNERS5
-rw-r--r--core/java/android/security/rkp/RemotelyProvisionedKey.aidl44
-rw-r--r--core/java/android/service/autofill/augmented/AugmentedAutofillService.java1
-rw-r--r--core/java/android/service/controls/ControlsProviderService.java12
-rw-r--r--core/java/android/service/notification/NotificationListenerService.java2
-rw-r--r--core/java/android/util/SparseArray.java12
-rw-r--r--core/java/android/view/Display.java33
-rw-r--r--core/java/android/view/DisplayEventReceiver.java9
-rw-r--r--core/java/android/view/DisplayInfo.java15
-rw-r--r--core/java/android/view/IWindowManager.aidl1
-rw-r--r--core/java/android/view/InsetsFrameProvider.java4
-rw-r--r--core/java/android/view/InsetsVisibilities.java134
-rw-r--r--core/java/android/view/KeyCharacterMap.java10
-rw-r--r--core/java/android/view/RemoteAnimationTarget.java29
-rw-r--r--core/java/android/view/SurfaceControl.java28
-rw-r--r--core/java/android/view/ViewRootImpl.java109
-rw-r--r--core/java/android/view/WindowInsets.java22
-rw-r--r--core/java/android/view/accessibility/AccessibilityNodeInfo.java30
-rw-r--r--core/java/android/view/accessibility/IAccessibilityManager.aidl4
-rw-r--r--core/java/android/widget/Editor.java5
-rw-r--r--core/java/android/widget/TextView.java194
-rw-r--r--core/java/android/window/BackEvent.java92
-rw-r--r--core/java/android/window/BackMotionEvent.aidl (renamed from core/java/android/window/BackEvent.aidl)2
-rw-r--r--core/java/android/window/BackMotionEvent.java150
-rw-r--r--core/java/android/window/BackProgressAnimator.java13
-rw-r--r--core/java/android/window/DisplayWindowPolicyController.java5
-rw-r--r--core/java/android/window/IOnBackInvokedCallback.aidl13
-rw-r--r--core/java/android/window/OnBackAnimationCallback.java19
-rw-r--r--core/java/android/window/OnBackInvokedCallback.java35
-rw-r--r--core/java/android/window/SnapshotDrawerUtils.java508
-rw-r--r--core/java/android/window/StartingWindowRemovalInfo.java9
-rw-r--r--core/java/android/window/TaskFragmentCreationParams.java66
-rw-r--r--core/java/android/window/WindowContainerToken.java2
-rw-r--r--core/java/android/window/WindowContainerTransaction.java43
-rw-r--r--core/java/android/window/WindowOnBackInvokedDispatcher.java8
-rw-r--r--core/java/com/android/internal/accessibility/AccessibilityShortcutController.java20
-rw-r--r--core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java6
-rw-r--r--core/java/com/android/internal/content/om/TEST_MAPPING24
-rw-r--r--core/java/com/android/internal/inputmethod/EditableInputConnection.java9
-rw-r--r--core/java/com/android/internal/jank/InteractionJankMonitor.java3
-rw-r--r--core/java/com/android/internal/os/anr/AnrLatencyTracker.java3
-rw-r--r--core/java/com/android/internal/power/ModemPowerProfile.java16
-rw-r--r--core/java/com/android/internal/statusbar/IStatusBar.aidl10
-rw-r--r--core/jni/Android.bp3
-rw-r--r--core/jni/TEST_MAPPING23
-rw-r--r--core/jni/android_view_DisplayEventReceiver.cpp8
-rw-r--r--core/jni/android_view_SurfaceControl.cpp31
-rw-r--r--core/proto/android/server/activitymanagerservice.proto17
-rw-r--r--core/res/Android.bp4
-rw-r--r--core/res/AndroidManifest.xml35
-rw-r--r--core/res/assets/geoid_height_map/README.md2
-rw-r--r--core/res/assets/geoid_height_map/map-params.pbbin0 -> 33 bytes
-rw-r--r--core/res/assets/geoid_height_map/tile-1.pbbin0 -> 10772 bytes
-rw-r--r--core/res/assets/geoid_height_map/tile-3.pbbin0 -> 13831 bytes
-rw-r--r--core/res/assets/geoid_height_map/tile-5.pbbin0 -> 12293 bytes
-rw-r--r--core/res/assets/geoid_height_map/tile-7.pbbin0 -> 12025 bytes
-rw-r--r--core/res/assets/geoid_height_map/tile-9.pbbin0 -> 10792 bytes
-rw-r--r--core/res/assets/geoid_height_map/tile-b.pbbin0 -> 9921 bytes
-rw-r--r--core/res/geoid_height_map_assets/README.md8
-rw-r--r--core/res/geoid_height_map_assets/map-params.textpb6
-rw-r--r--core/res/geoid_height_map_assets/tile-1.textpb3
-rw-r--r--core/res/geoid_height_map_assets/tile-3.textpb3
-rw-r--r--core/res/geoid_height_map_assets/tile-5.textpb3
-rw-r--r--core/res/geoid_height_map_assets/tile-7.textpb3
-rw-r--r--core/res/geoid_height_map_assets/tile-9.textpb3
-rw-r--r--core/res/geoid_height_map_assets/tile-b.textpb3
-rw-r--r--core/res/res/values-af/strings.xml1
-rw-r--r--core/res/res/values-am/strings.xml74
-rw-r--r--core/res/res/values-ar/strings.xml4
-rw-r--r--core/res/res/values-as/strings.xml1
-rw-r--r--core/res/res/values-az/strings.xml1
-rw-r--r--core/res/res/values-b+sr+Latn/strings.xml1
-rw-r--r--core/res/res/values-be/strings.xml1
-rw-r--r--core/res/res/values-bg/strings.xml1
-rw-r--r--core/res/res/values-bn/strings.xml1
-rw-r--r--core/res/res/values-bs/strings.xml1
-rw-r--r--core/res/res/values-ca/strings.xml73
-rw-r--r--core/res/res/values-cs/strings.xml1
-rw-r--r--core/res/res/values-da/strings.xml1
-rw-r--r--core/res/res/values-de/strings.xml73
-rw-r--r--core/res/res/values-el/strings.xml1
-rw-r--r--core/res/res/values-en-rAU/strings.xml1
-rw-r--r--core/res/res/values-en-rCA/strings.xml1
-rw-r--r--core/res/res/values-en-rGB/strings.xml1
-rw-r--r--core/res/res/values-en-rIN/strings.xml1
-rw-r--r--core/res/res/values-en-rXC/strings.xml1
-rw-r--r--core/res/res/values-es-rUS/strings.xml1
-rw-r--r--core/res/res/values-es/strings.xml75
-rw-r--r--core/res/res/values-et/strings.xml1
-rw-r--r--core/res/res/values-eu/strings.xml1
-rw-r--r--core/res/res/values-fa/strings.xml1
-rw-r--r--core/res/res/values-fi/strings.xml1
-rw-r--r--core/res/res/values-fr-rCA/strings.xml1
-rw-r--r--core/res/res/values-fr/strings.xml1
-rw-r--r--core/res/res/values-gl/strings.xml7
-rw-r--r--core/res/res/values-gu/strings.xml1
-rw-r--r--core/res/res/values-hi/strings.xml1
-rw-r--r--core/res/res/values-hr/strings.xml1
-rw-r--r--core/res/res/values-hu/strings.xml1
-rw-r--r--core/res/res/values-hy/strings.xml1
-rw-r--r--core/res/res/values-in/strings.xml1
-rw-r--r--core/res/res/values-is/strings.xml1
-rw-r--r--core/res/res/values-it/strings.xml1
-rw-r--r--core/res/res/values-iw/strings.xml1
-rw-r--r--core/res/res/values-ja/strings.xml1
-rw-r--r--core/res/res/values-ka/strings.xml1
-rw-r--r--core/res/res/values-kk/strings.xml73
-rw-r--r--core/res/res/values-km/strings.xml1
-rw-r--r--core/res/res/values-kn/strings.xml1
-rw-r--r--core/res/res/values-ko/strings.xml1
-rw-r--r--core/res/res/values-ky/strings.xml1
-rw-r--r--core/res/res/values-lo/strings.xml1
-rw-r--r--core/res/res/values-lt/strings.xml1
-rw-r--r--core/res/res/values-lv/strings.xml1
-rw-r--r--core/res/res/values-mk/strings.xml1
-rw-r--r--core/res/res/values-ml/strings.xml1
-rw-r--r--core/res/res/values-mn/strings.xml1
-rw-r--r--core/res/res/values-mr/strings.xml1
-rw-r--r--core/res/res/values-ms/strings.xml1
-rw-r--r--core/res/res/values-my/strings.xml1
-rw-r--r--core/res/res/values-nb/strings.xml1
-rw-r--r--core/res/res/values-ne/strings.xml1
-rw-r--r--core/res/res/values-nl/strings.xml9
-rw-r--r--core/res/res/values-or/strings.xml25
-rw-r--r--core/res/res/values-pa/strings.xml1
-rw-r--r--core/res/res/values-pl/strings.xml73
-rw-r--r--core/res/res/values-pt-rBR/strings.xml1
-rw-r--r--core/res/res/values-pt-rPT/strings.xml1
-rw-r--r--core/res/res/values-pt/strings.xml1
-rw-r--r--core/res/res/values-ro/strings.xml1
-rw-r--r--core/res/res/values-ru/strings.xml3
-rw-r--r--core/res/res/values-si/strings.xml1
-rw-r--r--core/res/res/values-sk/strings.xml1
-rw-r--r--core/res/res/values-sl/strings.xml1
-rw-r--r--core/res/res/values-sq/strings.xml1
-rw-r--r--core/res/res/values-sr/strings.xml1
-rw-r--r--core/res/res/values-sv/strings.xml1
-rw-r--r--core/res/res/values-sw/strings.xml73
-rw-r--r--core/res/res/values-ta/strings.xml73
-rw-r--r--core/res/res/values-te/strings.xml1
-rw-r--r--core/res/res/values-th/strings.xml1
-rw-r--r--core/res/res/values-tl/strings.xml1
-rw-r--r--core/res/res/values-tr/strings.xml1
-rw-r--r--core/res/res/values-uk/strings.xml5
-rw-r--r--core/res/res/values-ur/strings.xml1
-rw-r--r--core/res/res/values-uz/strings.xml1
-rw-r--r--core/res/res/values-vi/strings.xml1
-rw-r--r--core/res/res/values-zh-rCN/strings.xml1
-rw-r--r--core/res/res/values-zh-rHK/strings.xml1
-rw-r--r--core/res/res/values-zh-rTW/strings.xml1
-rw-r--r--core/res/res/values-zu/strings.xml1
-rw-r--r--core/res/res/values/attrs_manifest.xml26
-rw-r--r--core/res/res/values/config.xml4
-rw-r--r--core/res/res/values/config_telephony.xml5
-rw-r--r--core/res/res/values/public-staging.xml2
-rw-r--r--core/res/res/values/strings.xml33
-rw-r--r--core/res/res/values/symbols.xml1
-rw-r--r--core/res/res/xml/bookmarks.xml12
-rw-r--r--core/tests/GameManagerTests/src/android/app/GameModeConfigurationTest.java4
-rw-r--r--core/tests/GameManagerTests/src/android/app/GameModeInfoTest.java10
-rw-r--r--core/tests/coretests/src/android/app/backup/BackupManagerTest.java102
-rw-r--r--core/tests/coretests/src/android/os/PerformanceHintManagerTest.java3
-rw-r--r--core/tests/coretests/src/android/provider/DeviceConfigTest.java5
-rw-r--r--core/tests/coretests/src/android/provider/NameValueCacheTest.java6
-rw-r--r--core/tests/coretests/src/android/provider/SettingsProviderTest.java43
-rw-r--r--core/tests/coretests/src/android/view/InsetsVisibilitiesTest.java121
-rw-r--r--core/tests/coretests/src/android/window/SnapshotDrawerUtilsTest.java (renamed from libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/TaskSnapshotWindowTest.java)126
-rw-r--r--core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java13
-rw-r--r--core/tests/fuzzers/FuzzService/FuzzBinder.java7
-rw-r--r--core/tests/fuzzers/FuzzService/random_parcel_jni.cpp14
-rw-r--r--core/tests/fuzzers/FuzzService/random_parcel_jni.h2
-rw-r--r--core/tests/fuzzers/ParcelFuzzer/Android.bp40
-rw-r--r--core/tests/fuzzers/ParcelFuzzer/FuzzOperation.java22
-rw-r--r--core/tests/fuzzers/ParcelFuzzer/FuzzUtils.java87
-rw-r--r--core/tests/fuzzers/ParcelFuzzer/ParcelFuzzer.java42
-rw-r--r--core/tests/fuzzers/ParcelFuzzer/ReadOperation.java24
-rw-r--r--core/tests/fuzzers/ParcelFuzzer/ReadUtils.java435
-rw-r--r--core/tests/fuzzers/java_service_fuzzer/Android.bp8
-rw-r--r--core/tests/mockingcoretests/src/android/view/DisplayTest.java11
-rw-r--r--data/etc/services.core.protolog.json6
-rw-r--r--graphics/java/android/graphics/BaseCanvas.java16
-rw-r--r--graphics/java/android/graphics/BaseRecordingCanvas.java10
-rw-r--r--graphics/java/android/graphics/ImageFormat.java2
-rw-r--r--graphics/java/android/graphics/Mesh.java14
-rw-r--r--keystore/java/android/security/KeyStoreException.java48
-rw-r--r--keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java9
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java59
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java44
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java25
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java8
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java35
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java28
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java4
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java115
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java12
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java2
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java18
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java6
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java140
-rw-r--r--libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml41
-rw-r--r--libs/WindowManager/Shell/res/values-af/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-am/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-ar/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-as/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-az/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-be/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-bg/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-bn/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-bs/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-ca/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-cs/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-da/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-de/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-el/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-en-rAU/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-en-rCA/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-en-rGB/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-en-rIN/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-en-rXC/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-es-rUS/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-es/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-et/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-eu/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-fa/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-fi/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-fr-rCA/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-fr/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-gl/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-gu/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-hr/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-hu/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-hy/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-in/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-is/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-it/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-iw/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-ja/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-ka/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-kk/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-km/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-kn/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-ko/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-ky/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-lo/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-lt/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-lv/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-mk/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-mn/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-mr/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-ms/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-my/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-nb/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-nl/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-or/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-pa/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-pl/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-pt-rBR/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-pt-rPT/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-pt/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-ro/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-ru/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-si/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-sk/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-sl/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-sq/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-sr/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-sv/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-sw/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-te/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-th/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-tl/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-tr/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-uk/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-ur/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-uz/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-vi/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-zh-rCN/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-zh-rHK/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-zh-rTW/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-zu/strings.xml3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java116
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java77
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java22
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java33
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java30
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt33
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java36
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/freeform/OWNERS2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java15
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java48
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java118
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java67
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java117
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java35
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java196
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java16
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java120
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java423
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java128
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java4
-rw-r--r--libs/WindowManager/Shell/tests/flicker/AndroidTest.xml2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt45
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt134
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt23
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreen.kt10
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreen.kt10
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleFromLockScreen.kt10
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt10
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt10
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreenShellTransit.kt7
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt20
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt26
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt45
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt130
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt18
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt20
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt50
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt29
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt25
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt27
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt92
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt41
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt59
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt17
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt65
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt29
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTestShellTransit.kt6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt50
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest_ShellTransit.kt8
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt14
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt96
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/PipTestBase.kt5
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt65
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt88
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt82
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt98
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt88
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt87
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt88
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt87
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt77
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt10
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt18
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt200
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt45
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt45
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt45
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt90
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java9
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java3
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java122
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt26
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/OWNERS3
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java12
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModelTests.java5
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java63
-rw-r--r--libs/hwui/Android.bp3
-rw-r--r--libs/hwui/DisplayListOps.in1
-rw-r--r--libs/hwui/FrameInfo.h1
-rw-r--r--libs/hwui/RecordingCanvas.cpp23
-rw-r--r--libs/hwui/RecordingCanvas.h3
-rw-r--r--libs/hwui/SkiaCanvas.cpp4
-rw-r--r--libs/hwui/SkiaCanvas.h6
-rw-r--r--libs/hwui/apex/jni_runtime.cpp4
-rw-r--r--libs/hwui/hwui/BlurDrawLooper.cpp1
-rw-r--r--libs/hwui/hwui/Canvas.h1
-rw-r--r--libs/hwui/jni/MaskFilter.cpp1
-rw-r--r--libs/hwui/jni/Mesh.cpp37
-rw-r--r--libs/hwui/jni/MeshSpecification.cpp47
-rw-r--r--libs/hwui/jni/android_graphics_Canvas.cpp77
-rw-r--r--libs/hwui/renderthread/CacheManager.cpp1
-rw-r--r--libs/hwui/renderthread/CanvasContext.cpp58
-rw-r--r--libs/hwui/renderthread/CanvasContext.h48
-rw-r--r--libs/hwui/renderthread/DrawFrameTask.cpp159
-rw-r--r--libs/hwui/renderthread/DrawFrameTask.h29
-rw-r--r--libs/hwui/renderthread/HintSessionWrapper.cpp162
-rw-r--r--libs/hwui/renderthread/HintSessionWrapper.h57
-rw-r--r--libs/hwui/renderthread/RenderProxy.cpp14
-rw-r--r--libs/hwui/tests/common/TestContext.cpp6
-rw-r--r--libs/hwui/tests/unit/CanvasContextTests.cpp2
-rw-r--r--libs/hwui/tests/unit/RenderNodeDrawableTests.cpp10
-rw-r--r--libs/hwui/tests/unit/RenderNodeTests.cpp4
-rw-r--r--libs/hwui/tests/unit/SkiaDisplayListTests.cpp4
-rw-r--r--location/java/android/location/altitude/AltitudeConverter.java172
-rw-r--r--location/java/com/android/internal/location/altitude/GeoidHeightMap.java367
-rw-r--r--location/java/com/android/internal/location/altitude/S2CellIdUtils.java653
-rw-r--r--media/java/android/media/AudioHalVersionInfo.aidl18
-rw-r--r--media/java/android/media/AudioHalVersionInfo.java174
-rw-r--r--media/java/android/media/AudioManager.java7
-rw-r--r--media/java/android/media/AudioPresentation.java11
-rw-r--r--[-rwxr-xr-x]media/java/android/media/IAudioService.aidl3
-rw-r--r--media/java/android/media/MediaCodecInfo.java16
-rw-r--r--media/java/android/media/MediaRouter2Manager.java10
-rw-r--r--media/java/android/media/RingtoneManager.java13
-rw-r--r--media/tests/AudioPolicyTest/AndroidManifest.xml11
-rw-r--r--media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioPolicyDeathTest.java132
-rw-r--r--media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioPolicyDeathTestActivity.java132
-rw-r--r--media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioVolumeTestActivity.java (renamed from media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioPolicyTestActivity.java)4
-rw-r--r--media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioVolumesTestRule.java2
-rw-r--r--native/android/Android.bp1
-rw-r--r--native/android/performance_hint.cpp28
-rw-r--r--native/android/tests/performance_hint/PerformanceHintNativeTest.cpp11
-rw-r--r--packages/CarrierDefaultApp/res/values-af/strings.xml12
-rw-r--r--packages/CarrierDefaultApp/res/values-am/strings.xml12
-rw-r--r--packages/CarrierDefaultApp/res/values-ar/strings.xml12
-rw-r--r--packages/CarrierDefaultApp/res/values-as/strings.xml12
-rw-r--r--packages/CarrierDefaultApp/res/values-az/strings.xml12
-rw-r--r--packages/CarrierDefaultApp/res/values-b+sr+Latn/strings.xml12
-rw-r--r--packages/CarrierDefaultApp/res/values-be/strings.xml12
-rw-r--r--packages/CarrierDefaultApp/res/values-bg/strings.xml12
-rw-r--r--packages/CarrierDefaultApp/res/values-bn/strings.xml12
-rw-r--r--packages/CarrierDefaultApp/res/values-bs/strings.xml12
-rw-r--r--packages/CarrierDefaultApp/res/values-ca/strings.xml12
-rw-r--r--packages/CarrierDefaultApp/res/values-cs/strings.xml12
-rw-r--r--packages/CarrierDefaultApp/res/values-da/strings.xml12
-rw-r--r--packages/CarrierDefaultApp/res/values-de/strings.xml12
-rw-r--r--packages/CarrierDefaultApp/res/values-el/strings.xml12
-rw-r--r--packages/CarrierDefaultApp/res/values-en-rAU/strings.xml6
-rw-r--r--packages/CarrierDefaultApp/res/values-en-rCA/strings.xml12
-rw-r--r--packages/CarrierDefaultApp/res/values-en-rGB/strings.xml6
-rw-r--r--packages/CarrierDefaultApp/res/values-en-rIN/strings.xml6
-rw-r--r--packages/CarrierDefaultApp/res/values-en-rXC/strings.xml6
-rw-r--r--packages/CarrierDefaultApp/res/values-es-rUS/strings.xml12
-rw-r--r--packages/CarrierDefaultApp/res/values-es/strings.xml14
-rw-r--r--packages/CarrierDefaultApp/res/values-et/strings.xml12
-rw-r--r--packages/CarrierDefaultApp/res/values-eu/strings.xml12
-rw-r--r--packages/CarrierDefaultApp/res/values-fa/strings.xml12
-rw-r--r--packages/CarrierDefaultApp/res/values-fi/strings.xml12
-rw-r--r--packages/CarrierDefaultApp/res/values-fr-rCA/strings.xml12
-rw-r--r--packages/CarrierDefaultApp/res/values-fr/strings.xml12
-rw-r--r--packages/CarrierDefaultApp/res/values-gl/strings.xml12
-rw-r--r--packages/CarrierDefaultApp/res/values-gu/strings.xml12
-rw-r--r--packages/CarrierDefaultApp/res/values-hi/strings.xml12
-rw-r--r--packages/CarrierDefaultApp/res/values-hr/strings.xml12
-rw-r--r--packages/CarrierDefaultApp/res/values-hu/strings.xml14
-rw-r--r--packages/CarrierDefaultApp/res/values-hy/strings.xml12
-rw-r--r--packages/CarrierDefaultApp/res/values-in/strings.xml12
-rw-r--r--packages/CarrierDefaultApp/res/values-is/strings.xml12
-rw-r--r--packages/CarrierDefaultApp/res/values-it/strings.xml12
-rw-r--r--packages/CarrierDefaultApp/res/values-iw/strings.xml12
-rw-r--r--packages/CarrierDefaultApp/res/values-ja/strings.xml12
-rw-r--r--packages/CarrierDefaultApp/res/values-ka/strings.xml12
-rw-r--r--packages/CarrierDefaultApp/res/values-kk/strings.xml18
-rw-r--r--packages/CarrierDefaultApp/res/values-km/strings.xml12
-rw-r--r--packages/CarrierDefaultApp/res/values-kn/strings.xml12
-rw-r--r--packages/CarrierDefaultApp/res/values-ko/strings.xml12
-rw-r--r--packages/CarrierDefaultApp/res/values-ky/strings.xml16
-rw-r--r--packages/CarrierDefaultApp/res/values-lo/strings.xml12
-rw-r--r--packages/CarrierDefaultApp/res/values-lt/strings.xml12
-rw-r--r--packages/CarrierDefaultApp/res/values-lv/strings.xml12
-rw-r--r--packages/CarrierDefaultApp/res/values-mk/strings.xml12
-rw-r--r--packages/CarrierDefaultApp/res/values-ml/strings.xml12
-rw-r--r--packages/CarrierDefaultApp/res/values-mn/strings.xml14
-rw-r--r--packages/CarrierDefaultApp/res/values-mr/strings.xml12
-rw-r--r--packages/CarrierDefaultApp/res/values-ms/strings.xml14
-rw-r--r--packages/CarrierDefaultApp/res/values-my/strings.xml12
-rw-r--r--packages/CarrierDefaultApp/res/values-nb/strings.xml12
-rw-r--r--packages/CarrierDefaultApp/res/values-ne/strings.xml12
-rw-r--r--packages/CarrierDefaultApp/res/values-nl/strings.xml12
-rw-r--r--packages/CarrierDefaultApp/res/values-or/strings.xml12
-rw-r--r--packages/CarrierDefaultApp/res/values-pa/strings.xml12
-rw-r--r--packages/CarrierDefaultApp/res/values-pl/strings.xml12
-rw-r--r--packages/CarrierDefaultApp/res/values-pt-rBR/strings.xml12
-rw-r--r--packages/CarrierDefaultApp/res/values-pt-rPT/strings.xml6
-rw-r--r--packages/CarrierDefaultApp/res/values-pt/strings.xml12
-rw-r--r--packages/CarrierDefaultApp/res/values-ro/strings.xml22
-rw-r--r--packages/CarrierDefaultApp/res/values-ru/strings.xml12
-rw-r--r--packages/CarrierDefaultApp/res/values-si/strings.xml12
-rw-r--r--packages/CarrierDefaultApp/res/values-sk/strings.xml6
-rw-r--r--packages/CarrierDefaultApp/res/values-sl/strings.xml12
-rw-r--r--packages/CarrierDefaultApp/res/values-sq/strings.xml12
-rw-r--r--packages/CarrierDefaultApp/res/values-sr/strings.xml12
-rw-r--r--packages/CarrierDefaultApp/res/values-sv/strings.xml12
-rw-r--r--packages/CarrierDefaultApp/res/values-sw/strings.xml12
-rw-r--r--packages/CarrierDefaultApp/res/values-ta/strings.xml12
-rw-r--r--packages/CarrierDefaultApp/res/values-te/strings.xml16
-rw-r--r--packages/CarrierDefaultApp/res/values-th/strings.xml12
-rw-r--r--packages/CarrierDefaultApp/res/values-tl/strings.xml12
-rw-r--r--packages/CarrierDefaultApp/res/values-tr/strings.xml12
-rw-r--r--packages/CarrierDefaultApp/res/values-uk/strings.xml12
-rw-r--r--packages/CarrierDefaultApp/res/values-ur/strings.xml14
-rw-r--r--packages/CarrierDefaultApp/res/values-uz/strings.xml12
-rw-r--r--packages/CarrierDefaultApp/res/values-vi/strings.xml12
-rw-r--r--packages/CarrierDefaultApp/res/values-zh-rCN/strings.xml12
-rw-r--r--packages/CarrierDefaultApp/res/values-zh-rHK/strings.xml12
-rw-r--r--packages/CarrierDefaultApp/res/values-zh-rTW/strings.xml12
-rw-r--r--packages/CarrierDefaultApp/res/values-zu/strings.xml12
-rw-r--r--packages/CompanionDeviceManager/res/values-af/strings.xml33
-rw-r--r--packages/CompanionDeviceManager/res/values-am/strings.xml33
-rw-r--r--packages/CompanionDeviceManager/res/values-ar/strings.xml33
-rw-r--r--packages/CompanionDeviceManager/res/values-as/strings.xml33
-rw-r--r--packages/CompanionDeviceManager/res/values-az/strings.xml33
-rw-r--r--packages/CompanionDeviceManager/res/values-b+sr+Latn/strings.xml33
-rw-r--r--packages/CompanionDeviceManager/res/values-be/strings.xml33
-rw-r--r--packages/CompanionDeviceManager/res/values-bg/strings.xml33
-rw-r--r--packages/CompanionDeviceManager/res/values-bn/strings.xml33
-rw-r--r--packages/CompanionDeviceManager/res/values-bs/strings.xml33
-rw-r--r--packages/CompanionDeviceManager/res/values-ca/strings.xml33
-rw-r--r--packages/CompanionDeviceManager/res/values-cs/strings.xml33
-rw-r--r--packages/CompanionDeviceManager/res/values-da/strings.xml33
-rw-r--r--packages/CompanionDeviceManager/res/values-de/strings.xml33
-rw-r--r--packages/CompanionDeviceManager/res/values-el/strings.xml33
-rw-r--r--packages/CompanionDeviceManager/res/values-en-rAU/strings.xml33
-rw-r--r--packages/CompanionDeviceManager/res/values-en-rCA/strings.xml33
-rw-r--r--packages/CompanionDeviceManager/res/values-en-rGB/strings.xml33
-rw-r--r--packages/CompanionDeviceManager/res/values-en-rIN/strings.xml33
-rw-r--r--packages/CompanionDeviceManager/res/values-en-rXC/strings.xml33
-rw-r--r--packages/CompanionDeviceManager/res/values-es-rUS/strings.xml33
-rw-r--r--packages/CompanionDeviceManager/res/values-es/strings.xml33
-rw-r--r--packages/CompanionDeviceManager/res/values-et/strings.xml33
-rw-r--r--packages/CompanionDeviceManager/res/values-eu/strings.xml33
-rw-r--r--packages/CompanionDeviceManager/res/values-fa/strings.xml33
-rw-r--r--packages/CompanionDeviceManager/res/values-fi/strings.xml33
-rw-r--r--packages/CompanionDeviceManager/res/values-fr-rCA/strings.xml33
-rw-r--r--packages/CompanionDeviceManager/res/values-fr/strings.xml33
-rw-r--r--packages/CompanionDeviceManager/res/values-gl/strings.xml33
-rw-r--r--packages/CompanionDeviceManager/res/values-gu/strings.xml33
-rw-r--r--packages/CompanionDeviceManager/res/values-hr/strings.xml33
-rw-r--r--packages/CompanionDeviceManager/res/values-hu/strings.xml33
-rw-r--r--packages/CompanionDeviceManager/res/values-hy/strings.xml33
-rw-r--r--packages/CompanionDeviceManager/res/values-in/strings.xml33
-rw-r--r--packages/CompanionDeviceManager/res/values-is/strings.xml33
-rw-r--r--packages/CompanionDeviceManager/res/values-it/strings.xml33
-rw-r--r--packages/CompanionDeviceManager/res/values-iw/strings.xml33
-rw-r--r--packages/CompanionDeviceManager/res/values-ja/strings.xml33
-rw-r--r--packages/CompanionDeviceManager/res/values-ka/strings.xml33
-rw-r--r--packages/CompanionDeviceManager/res/values-kk/strings.xml33
-rw-r--r--packages/CompanionDeviceManager/res/values-km/strings.xml33
-rw-r--r--packages/CompanionDeviceManager/res/values-kn/strings.xml33
-rw-r--r--packages/CompanionDeviceManager/res/values-ko/strings.xml33
-rw-r--r--packages/CompanionDeviceManager/res/values-ky/strings.xml33
-rw-r--r--packages/CompanionDeviceManager/res/values-lo/strings.xml33
-rw-r--r--packages/CompanionDeviceManager/res/values-lt/strings.xml33
-rw-r--r--packages/CompanionDeviceManager/res/values-lv/strings.xml33
-rw-r--r--packages/CompanionDeviceManager/res/values-mk/strings.xml33
-rw-r--r--packages/CompanionDeviceManager/res/values-mn/strings.xml33
-rw-r--r--packages/CompanionDeviceManager/res/values-mr/strings.xml33
-rw-r--r--packages/CompanionDeviceManager/res/values-ms/strings.xml33
-rw-r--r--packages/CompanionDeviceManager/res/values-my/strings.xml33
-rw-r--r--packages/CompanionDeviceManager/res/values-nb/strings.xml33
-rw-r--r--packages/CompanionDeviceManager/res/values-nl/strings.xml33
-rw-r--r--packages/CompanionDeviceManager/res/values-or/strings.xml33
-rw-r--r--packages/CompanionDeviceManager/res/values-pa/strings.xml33
-rw-r--r--packages/CompanionDeviceManager/res/values-pl/strings.xml33
-rw-r--r--packages/CompanionDeviceManager/res/values-pt-rBR/strings.xml33
-rw-r--r--packages/CompanionDeviceManager/res/values-pt-rPT/strings.xml33
-rw-r--r--packages/CompanionDeviceManager/res/values-pt/strings.xml33
-rw-r--r--packages/CompanionDeviceManager/res/values-ro/strings.xml33
-rw-r--r--packages/CompanionDeviceManager/res/values-ru/strings.xml33
-rw-r--r--packages/CompanionDeviceManager/res/values-si/strings.xml33
-rw-r--r--packages/CompanionDeviceManager/res/values-sk/strings.xml33
-rw-r--r--packages/CompanionDeviceManager/res/values-sl/strings.xml33
-rw-r--r--packages/CompanionDeviceManager/res/values-sq/strings.xml33
-rw-r--r--packages/CompanionDeviceManager/res/values-sr/strings.xml33
-rw-r--r--packages/CompanionDeviceManager/res/values-sv/strings.xml33
-rw-r--r--packages/CompanionDeviceManager/res/values-sw/strings.xml33
-rw-r--r--packages/CompanionDeviceManager/res/values-te/strings.xml33
-rw-r--r--packages/CompanionDeviceManager/res/values-th/strings.xml33
-rw-r--r--packages/CompanionDeviceManager/res/values-tl/strings.xml33
-rw-r--r--packages/CompanionDeviceManager/res/values-tr/strings.xml33
-rw-r--r--packages/CompanionDeviceManager/res/values-uk/strings.xml33
-rw-r--r--packages/CompanionDeviceManager/res/values-ur/strings.xml33
-rw-r--r--packages/CompanionDeviceManager/res/values-uz/strings.xml33
-rw-r--r--packages/CompanionDeviceManager/res/values-vi/strings.xml33
-rw-r--r--packages/CompanionDeviceManager/res/values-zh-rCN/strings.xml33
-rw-r--r--packages/CompanionDeviceManager/res/values-zh-rHK/strings.xml33
-rw-r--r--packages/CompanionDeviceManager/res/values-zh-rTW/strings.xml33
-rw-r--r--packages/CompanionDeviceManager/res/values-zu/strings.xml33
-rw-r--r--packages/CredentialManager/res/drawable/ic_passkeys_onboarding.pngbin0 -> 12179 bytes
-rw-r--r--packages/CredentialManager/res/drawable/ic_passkeys_onboarding_device.xml32
-rw-r--r--packages/CredentialManager/res/drawable/ic_passkeys_onboarding_fingerprint.xml31
-rw-r--r--packages/CredentialManager/res/drawable/ic_passkeys_onboarding_password.xml31
-rw-r--r--packages/CredentialManager/res/values-af/strings.xml8
-rw-r--r--packages/CredentialManager/res/values-am/strings.xml2
-rw-r--r--packages/CredentialManager/res/values-ar/strings.xml7
-rw-r--r--packages/CredentialManager/res/values-as/strings.xml11
-rw-r--r--packages/CredentialManager/res/values-az/strings.xml8
-rw-r--r--packages/CredentialManager/res/values-b+sr+Latn/strings.xml2
-rw-r--r--packages/CredentialManager/res/values-be/strings.xml2
-rw-r--r--packages/CredentialManager/res/values-bg/strings.xml2
-rw-r--r--packages/CredentialManager/res/values-bn/strings.xml2
-rw-r--r--packages/CredentialManager/res/values-bs/strings.xml8
-rw-r--r--packages/CredentialManager/res/values-ca/strings.xml5
-rw-r--r--packages/CredentialManager/res/values-cs/strings.xml2
-rw-r--r--packages/CredentialManager/res/values-da/strings.xml2
-rw-r--r--packages/CredentialManager/res/values-de/strings.xml8
-rw-r--r--packages/CredentialManager/res/values-el/strings.xml8
-rw-r--r--packages/CredentialManager/res/values-en-rAU/strings.xml25
-rw-r--r--packages/CredentialManager/res/values-en-rCA/strings.xml19
-rw-r--r--packages/CredentialManager/res/values-en-rGB/strings.xml25
-rw-r--r--packages/CredentialManager/res/values-en-rIN/strings.xml25
-rw-r--r--packages/CredentialManager/res/values-en-rXC/strings.xml19
-rw-r--r--packages/CredentialManager/res/values-es-rUS/strings.xml8
-rw-r--r--packages/CredentialManager/res/values-es/strings.xml2
-rw-r--r--packages/CredentialManager/res/values-et/strings.xml2
-rw-r--r--packages/CredentialManager/res/values-eu/strings.xml2
-rw-r--r--packages/CredentialManager/res/values-fa/strings.xml2
-rw-r--r--packages/CredentialManager/res/values-fi/strings.xml5
-rw-r--r--packages/CredentialManager/res/values-fr-rCA/strings.xml8
-rw-r--r--packages/CredentialManager/res/values-fr/strings.xml8
-rw-r--r--packages/CredentialManager/res/values-gl/strings.xml11
-rw-r--r--packages/CredentialManager/res/values-gu/strings.xml8
-rw-r--r--packages/CredentialManager/res/values-hi/strings.xml2
-rw-r--r--packages/CredentialManager/res/values-hr/strings.xml2
-rw-r--r--packages/CredentialManager/res/values-hu/strings.xml8
-rw-r--r--packages/CredentialManager/res/values-hy/strings.xml2
-rw-r--r--packages/CredentialManager/res/values-in/strings.xml8
-rw-r--r--packages/CredentialManager/res/values-is/strings.xml2
-rw-r--r--packages/CredentialManager/res/values-it/strings.xml8
-rw-r--r--packages/CredentialManager/res/values-iw/strings.xml8
-rw-r--r--packages/CredentialManager/res/values-ja/strings.xml8
-rw-r--r--packages/CredentialManager/res/values-ka/strings.xml8
-rw-r--r--packages/CredentialManager/res/values-kk/strings.xml5
-rw-r--r--packages/CredentialManager/res/values-km/strings.xml2
-rw-r--r--packages/CredentialManager/res/values-kn/strings.xml8
-rw-r--r--packages/CredentialManager/res/values-ko/strings.xml2
-rw-r--r--packages/CredentialManager/res/values-ky/strings.xml5
-rw-r--r--packages/CredentialManager/res/values-lo/strings.xml2
-rw-r--r--packages/CredentialManager/res/values-lt/strings.xml8
-rw-r--r--packages/CredentialManager/res/values-lv/strings.xml11
-rw-r--r--packages/CredentialManager/res/values-mk/strings.xml8
-rw-r--r--packages/CredentialManager/res/values-ml/strings.xml8
-rw-r--r--packages/CredentialManager/res/values-mn/strings.xml8
-rw-r--r--packages/CredentialManager/res/values-mr/strings.xml2
-rw-r--r--packages/CredentialManager/res/values-ms/strings.xml8
-rw-r--r--packages/CredentialManager/res/values-my/strings.xml2
-rw-r--r--packages/CredentialManager/res/values-nb/strings.xml5
-rw-r--r--packages/CredentialManager/res/values-ne/strings.xml2
-rw-r--r--packages/CredentialManager/res/values-nl/strings.xml11
-rw-r--r--packages/CredentialManager/res/values-or/strings.xml2
-rw-r--r--packages/CredentialManager/res/values-pa/strings.xml5
-rw-r--r--packages/CredentialManager/res/values-pl/strings.xml2
-rw-r--r--packages/CredentialManager/res/values-pt-rBR/strings.xml8
-rw-r--r--packages/CredentialManager/res/values-pt-rPT/strings.xml19
-rw-r--r--packages/CredentialManager/res/values-pt/strings.xml8
-rw-r--r--packages/CredentialManager/res/values-ro/strings.xml2
-rw-r--r--packages/CredentialManager/res/values-ru/strings.xml11
-rw-r--r--packages/CredentialManager/res/values-si/strings.xml5
-rw-r--r--packages/CredentialManager/res/values-sk/strings.xml28
-rw-r--r--packages/CredentialManager/res/values-sl/strings.xml8
-rw-r--r--packages/CredentialManager/res/values-sq/strings.xml5
-rw-r--r--packages/CredentialManager/res/values-sr/strings.xml2
-rw-r--r--packages/CredentialManager/res/values-sv/strings.xml11
-rw-r--r--packages/CredentialManager/res/values-sw/strings.xml63
-rw-r--r--packages/CredentialManager/res/values-ta/strings.xml2
-rw-r--r--packages/CredentialManager/res/values-te/strings.xml2
-rw-r--r--packages/CredentialManager/res/values-th/strings.xml8
-rw-r--r--packages/CredentialManager/res/values-tl/strings.xml8
-rw-r--r--packages/CredentialManager/res/values-tr/strings.xml4
-rw-r--r--packages/CredentialManager/res/values-uk/strings.xml5
-rw-r--r--packages/CredentialManager/res/values-ur/strings.xml8
-rw-r--r--packages/CredentialManager/res/values-uz/strings.xml2
-rw-r--r--packages/CredentialManager/res/values-vi/strings.xml2
-rw-r--r--packages/CredentialManager/res/values-zh-rCN/strings.xml2
-rw-r--r--packages/CredentialManager/res/values-zh-rHK/strings.xml8
-rw-r--r--packages/CredentialManager/res/values-zh-rTW/strings.xml8
-rw-r--r--packages/CredentialManager/res/values-zu/strings.xml2
-rw-r--r--packages/CredentialManager/res/values/strings.xml10
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt56
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt2
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt59
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/common/material/ModalBottomSheet.kt5
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/common/ui/CancelButton.kt9
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/common/ui/Cards.kt50
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/common/ui/ConfirmButton.kt10
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt6
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/common/ui/Texts.kt84
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt1478
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt104
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt1
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt725
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt63
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCredentialOption.kt20
-rw-r--r--packages/InputDevices/res/raw/keyboard_layout_english_us_intl.kcm8
-rw-r--r--packages/SettingsLib/MainSwitchPreference/Android.bp1
-rw-r--r--packages/SettingsLib/Spa/gallery/AndroidManifest.xml2
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/IllustrationPage.kt6
-rw-r--r--packages/SettingsLib/Spa/screenshot/Android.bp41
-rw-r--r--packages/SettingsLib/Spa/screenshot/AndroidManifest.xml32
-rw-r--r--packages/SettingsLib/Spa/screenshot/AndroidTest.xml36
-rw-r--r--packages/SettingsLib/Spa/screenshot/assets/phone/dark_landscape_preference.pngbin0 -> 41801 bytes
-rw-r--r--packages/SettingsLib/Spa/screenshot/assets/phone/dark_portrait_preference.pngbin0 -> 76377 bytes
-rw-r--r--packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_preference.pngbin0 -> 41639 bytes
-rw-r--r--packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_preference.pngbin0 -> 76120 bytes
-rw-r--r--packages/SettingsLib/Spa/screenshot/assets/tablet/dark_landscape_preference.pngbin0 -> 47896 bytes
-rw-r--r--packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_preference.pngbin0 -> 50068 bytes
-rw-r--r--packages/SettingsLib/Spa/screenshot/assets/tablet/light_landscape_preference.pngbin0 -> 47669 bytes
-rw-r--r--packages/SettingsLib/Spa/screenshot/assets/tablet/light_portrait_preference.pngbin0 -> 49889 bytes
-rw-r--r--packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/Bitmap.kt66
-rw-r--r--packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/DefaultDeviceEmulationSpec.kt66
-rw-r--r--packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsGoldenImagePathManager.kt44
-rw-r--r--packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsScreenshotTestRule.kt91
-rw-r--r--packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/PreferenceScreenshotTest.kt91
-rw-r--r--packages/SettingsLib/Spa/settings.gradle1
-rw-r--r--packages/SettingsLib/Spa/spa/build.gradle39
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugFormat.kt2
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugProvider.kt4
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/ProviderColumn.kt (renamed from packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/ProviderColumn.kt)53
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt20
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt2
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/DrawablePainter.kt19
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/FlowExt.kt189
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/Keyboards.kt3
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/Pager.kt39
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/PagerState.kt26
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/EntryLogger.kt7
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Flows.kt39
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/MessageFormats.kt34
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/PageLogger.kt5
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Parameter.kt7
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/StateFlowBridge.kt42
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/search/SpaSearchContract.kt97
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/search/SpaSearchProvider.kt76
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/provider/Demo.kt6
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/illustration/Illustration.kt (renamed from packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/Illustration.kt)2
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt5
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt3
-rw-r--r--packages/SettingsLib/Spa/tests/AndroidManifest.xml4
-rw-r--r--packages/SettingsLib/Spa/tests/build.gradle65
-rw-r--r--packages/SettingsLib/Spa/tests/res/values/strings.xml28
-rw-r--r--packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/compose/KeyboardsTest.kt100
-rw-r--r--packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/FlowsTest.kt90
-rw-r--r--packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/MessageFormatsTest.kt66
-rw-r--r--packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/ParameterTest.kt7
-rw-r--r--packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/StateFlowBridgeTest.kt67
-rw-r--r--packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/search/SpaSearchProviderTest.kt109
-rw-r--r--packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/UniqueIdHelper.kt2
-rw-r--r--packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/button/ActionButtonsTest.kt17
-rw-r--r--packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/chart/ChartTest.kt (renamed from packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ChartTest.kt)14
-rw-r--r--packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/illustration/IllustrationTest.kt (renamed from packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/IllustrationTest.kt)16
-rw-r--r--packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/HomeScaffoldTest.kt61
-rw-r--r--packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/FooterTest.kt55
-rw-r--r--packages/SettingsLib/Spa/testutils/Android.bp2
-rw-r--r--packages/SettingsLib/Spa/testutils/build.gradle8
-rw-r--r--packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/FakeNavControllerWrapper.kt42
-rw-r--r--packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/FlowTestUtil.kt (renamed from packages/SystemUI/src/com/android/systemui/qs/NewFooterActionsController.kt)21
-rw-r--r--packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/LiveDataTestUtil.kt50
-rw-r--r--packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/MockitoHelper.kt (renamed from packages/SettingsLib/Spa/testutils/src/MockitoHelper.kt)0
-rw-r--r--packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/SpaTest.kt (renamed from packages/SettingsLib/Spa/testutils/src/SpaTest.kt)0
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-af/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-am/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-ar/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-as/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-az/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-b+sr+Latn/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-be/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-bg/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-bn/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-bs/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-ca/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-cs/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-da/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-de/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-el/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-en-rAU/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-en-rGB/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-en-rIN/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-es-rUS/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-es/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-et/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-eu/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-fa/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-fi/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-fr-rCA/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-fr/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-gl/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-gu/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-hi/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-hr/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-hu/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-hy/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-in/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-is/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-it/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-iw/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-ja/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-ka/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-kk/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-km/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-kn/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-ko/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-ky/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-lo/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-lt/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-lv/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-mk/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-ml/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-mn/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-mr/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-ms/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-my/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-nb/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-ne/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-nl/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-or/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-pa/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-pl/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-pt-rBR/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-pt-rPT/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-pt/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-ro/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-ru/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-si/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-sk/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-sl/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-sq/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-sr/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-sv/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-sw/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-ta/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-te/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-th/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-tl/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-tr/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-uk/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-ur/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-uz/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-vi/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-zh-rCN/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-zh-rHK/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-zh-rTW/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-zu/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt2
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt28
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt2
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfoPage.kt19
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt15
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListItem.kt2
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt17
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt26
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt77
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt11
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/AndroidManifest.xml4
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/res/values/strings.xml11
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/compose/DisposableBroadcastReceiverAsUserTest.kt108
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppInfoTest.kt2
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListPageTest.kt6
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListSwitchItemTest.kt189
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt4
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt264
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppStorageSizeTest.kt2
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPageTest.kt153
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt4
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListTest.kt11
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceTest.kt2
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestTogglePermissionAppListModel.kt11
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestTogglePermissionAppListProvider.kt25
-rw-r--r--packages/SettingsLib/res/values-af/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-am/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-ar/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-as/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-az/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-b+sr+Latn/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-be/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-bg/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-bn/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-bs/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-ca/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-cs/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-da/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-de/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-el/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-en-rAU/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-en-rCA/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-en-rGB/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-en-rIN/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-en-rXC/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-es-rUS/strings.xml4
-rw-r--r--packages/SettingsLib/res/values-es/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-et/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-eu/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-fa/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-fi/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-fr-rCA/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-fr/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-gl/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-gu/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-hi/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-hr/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-hu/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-hy/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-in/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-is/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-it/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-iw/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-ja/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-ka/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-kk/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-km/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-kn/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-ko/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-ky/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-lo/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-lt/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-lv/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-mk/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-ml/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-mn/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-mr/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-ms/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-my/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-nb/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-ne/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-nl/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-or/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-pa/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-pl/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-pt-rBR/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-pt-rPT/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-pt/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-ro/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-ru/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-si/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-sk/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-sl/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-sq/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-sr/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-sv/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-sw/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-ta/strings.xml2
-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/res/values-tl/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-tr/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-uk/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-ur/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-uz/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-vi/strings.xml8
-rw-r--r--packages/SettingsLib/res/values-zh-rCN/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-zh-rHK/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-zh-rTW/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-zu/strings.xml2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java10
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java12
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java16
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java2
-rw-r--r--packages/SettingsProvider/res/xml/bookmarks.xml10
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java1
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java8
-rw-r--r--packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java4
-rw-r--r--packages/SettingsProvider/test/src/com/android/providers/settings/DeviceConfigServiceTest.java8
-rw-r--r--packages/Shell/AndroidManifest.xml9
-rw-r--r--packages/SystemUI/AndroidManifest.xml23
-rw-r--r--packages/SystemUI/animation/Android.bp27
-rw-r--r--packages/SystemUI/animation/TEST_MAPPING7
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt12
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/MultiRippleController.kt (renamed from packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/MultiRippleController.kt)0
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/MultiRippleView.kt (renamed from packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/MultiRippleView.kt)4
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt (renamed from packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt)4
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationConfig.kt (renamed from packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationConfig.kt)0
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt (renamed from packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt)2
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt (renamed from packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt)0
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/SdfShaderLibrary.kt (renamed from packages/SystemUI/src/com/android/systemui/surfaceeffects/shaderutil/SdfShaderLibrary.kt)0
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/ShaderUtilLibrary.kt (renamed from packages/SystemUI/src/com/android/systemui/surfaceeffects/shaderutil/ShaderUtilLibrary.kt)0
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt (renamed from packages/SystemUI/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt)0
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseController.kt (renamed from packages/SystemUI/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseController.kt)0
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt (renamed from packages/SystemUI/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt)0
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt (renamed from packages/SystemUI/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt)2
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt24
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt2
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/FakeKeyguardQuickAffordanceProviderClient.kt16
-rw-r--r--packages/SystemUI/res-keyguard/layout/fgs_footer.xml105
-rw-r--r--packages/SystemUI/res-keyguard/layout/footer_actions.xml76
-rw-r--r--packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher.xml2
-rw-r--r--packages/SystemUI/res-keyguard/values-nl/strings.xml2
-rw-r--r--packages/SystemUI/res/drawable/ic_circle_check_box.xml2
-rw-r--r--packages/SystemUI/res/drawable/ic_note_task_button.xml28
-rw-r--r--packages/SystemUI/res/drawable/media_output_dialog_seekbar_background.xml5
-rw-r--r--packages/SystemUI/res/drawable/media_output_icon_volume_off.xml27
-rw-r--r--packages/SystemUI/res/drawable/media_output_item_check_box.xml26
-rw-r--r--packages/SystemUI/res/drawable/media_output_status_checked.xml26
-rw-r--r--packages/SystemUI/res/drawable/media_output_status_filled_checked.xml (renamed from packages/SystemUI/res/drawable/media_output_status_check.xml)0
-rw-r--r--packages/SystemUI/res/drawable/media_output_status_selectable.xml26
-rw-r--r--packages/SystemUI/res/drawable/media_output_title_icon_area.xml25
-rw-r--r--packages/SystemUI/res/layout/media_output_list_item_advanced.xml156
-rw-r--r--packages/SystemUI/res/layout/media_session_view.xml1
-rw-r--r--packages/SystemUI/res/layout/quick_settings_security_footer.xml61
-rw-r--r--packages/SystemUI/res/layout/status_bar_no_notifications.xml27
-rw-r--r--packages/SystemUI/res/layout/super_notification_shade.xml18
-rw-r--r--packages/SystemUI/res/values-sw600dp/config.xml3
-rw-r--r--packages/SystemUI/res/values/colors.xml3
-rw-r--r--packages/SystemUI/res/values/config.xml2
-rw-r--r--packages/SystemUI/res/values/dimens.xml4
-rw-r--r--packages/SystemUI/res/values/flags.xml4
-rw-r--r--packages/SystemUI/res/values/strings.xml14
-rw-r--r--packages/SystemUI/res/xml/combined_qs_header_scene.xml17
-rw-r--r--packages/SystemUI/res/xml/qqs_header.xml3
-rw-r--r--packages/SystemUI/res/xml/qs_header.xml2
-rw-r--r--packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt2
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt1
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/condition/CombinedCondition.kt (renamed from packages/SystemUI/src/com/android/systemui/util/condition/CombinedCondition.kt)2
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/condition/Condition.java (renamed from packages/SystemUI/src/com/android/systemui/util/condition/Condition.java)50
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/condition/Evaluator.kt (renamed from packages/SystemUI/src/com/android/systemui/util/condition/Evaluator.kt)18
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/condition/Monitor.java (renamed from packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java)12
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java4
-rw-r--r--packages/SystemUI/src/com/android/keyguard/FaceAuthReason.kt11
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java3
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java4
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt9
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java3
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java47
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java18
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java28
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java151
-rw-r--r--packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/CoreStartable.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/ScreenDecorations.java30
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java26
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlay.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/common/shared/model/TintedIcon.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/common/ui/binder/TintedIconViewBinder.kt42
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsDialogManager.kt231
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsRepository.kt (renamed from packages/SystemUI/src/com/android/systemui/controls/ControlsSettingsRepository.kt)2
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsRepositoryImpl.kt (renamed from packages/SystemUI/src/com/android/systemui/controls/ControlsSettingsRepositoryImpl.kt)2
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt110
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugRestarter.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseRestarter.kt43
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/Flags.kt28
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/Restarter.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/SystemExitRestarter.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt170
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt149
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodToGoneTransitionInteractor.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingLockscreenTransitionInteractor.kt94
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingToAodTransitionInteractor.kt76
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingTransitionInteractor.kt180
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GoneAodTransitionInteractor.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt95
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt45
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenGoneTransitionInteractor.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BiometricUnlockModel.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BiometricUnlockSource.kt39
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/shared/model/DozeStateModel.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakeSleepReason.kt44
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt34
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessState.kt44
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/LightRevealScrimViewBinder.kt43
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LightRevealScrimViewModel.kt31
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/SessionTracker.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java26
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/util/MediaUiEventLogger.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java60
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java168
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java27
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSeekbar.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt81
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt79
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt95
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt51
-rw-r--r--packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt41
-rw-r--r--packages/SystemUI/src/com/android/systemui/notetask/shortcut/CreateNoteTaskShortcutActivity.kt79
-rw-r--r--packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt53
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt230
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt266
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/FooterActionsView.kt135
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSFgsManagerFooter.java178
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSFragment.java66
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java262
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooterUtils.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java36
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java25
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/security/data/model/SecurityModel.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java83
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java36
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java60
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java150
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java21
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt33
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt164
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/Pluggable.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/SeenNotificationsProvider.kt41
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java37
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java89
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java35
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java71
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java72
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt50
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java249
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java123
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java55
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelper.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java64
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java71
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java48
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java47
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileController.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java65
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java171
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt72
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java110
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/DataConnectionState.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModel.kt (renamed from packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileSubscriptionModel.kt)5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/ResolvedNetworkType.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SubscriptionModel.kt32
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt217
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt250
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt155
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt262
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoModeMobileConnectionDataSource.kt141
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/model/FakeNetworkEventModel.kt43
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt253
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt281
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt32
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt29
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt35
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiActivityModel.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/WifiUiAdapter.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/model/WifiIcon.kt56
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/HomeWifiViewModel.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/KeyguardWifiViewModel.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/QsWifiViewModel.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt76
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt72
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/DeviceConfigProxy.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/TraceUtils.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java20
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt145
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt136
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt10
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java272
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/animation/InterpolatorsAndroidXTest.kt (renamed from packages/SystemUI/animation/tests/com/android/systemui/animation/InterpolatorsAndroidXTest.kt)3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java11
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt53
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/controls/settings/ControlsSettingsDialogManagerImplTest.kt328
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/controls/settings/ControlsSettingsRepositoryImplTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/controls/ControlsSettingsRepositoryImplTest.kt)2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/controls/settings/FakeControlsSettingsRepository.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/controls/FakeControlsSettingsRepository.kt)2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt222
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugRestarterTest.kt10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseRestarterTest.kt20
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java51
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt160
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt167
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt154
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/log/SessionTrackerTest.java30
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java102
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java18
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt78
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt224
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt83
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt37
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/FgsManagerControllerTest.java179
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt440
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java63
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java386
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt82
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java22
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt31
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt45
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java72
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionMonitorTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionMonitorTest.java)49
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionTest.java)67
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shared/condition/FakeCondition.java (renamed from packages/SystemUI/tests/utils/src/com/android/systemui/util/condition/FakeCondition.java)4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/RoundableTest.kt164
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt36
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt14
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java23
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java12
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelperTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImplTest.kt88
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt101
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java131
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest_Old.java641
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt13
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt63
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt225
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt249
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt325
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepositoryTest.kt)92
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepositoryTest.kt)76
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt77
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt46
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt28
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt13
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt32
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/util/TestableAlertDialogTest.kt333
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java28
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/MemoryTrackingTestCase.java63
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt43
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeLightRevealScrimRepository.kt33
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeFgsManagerController.kt5
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/util/DeviceConfigProxyFake.java5
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/util/TestableAlertDialog.kt141
-rw-r--r--packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt3
-rw-r--r--packages/VpnDialogs/res/values-ar/strings.xml6
-rw-r--r--proto/src/OWNERS1
-rw-r--r--proto/src/altitude.proto54
-rw-r--r--services/Android.bp1
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java10
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java64
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java57
-rw-r--r--services/api/current.txt10
-rw-r--r--services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java43
-rw-r--r--services/companion/java/com/android/server/companion/virtual/InputController.java31
-rw-r--r--services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java86
-rw-r--r--services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java18
-rw-r--r--services/core/Android.bp2
-rw-r--r--services/core/java/com/android/server/BinaryTransparencyService.java164
-rw-r--r--services/core/java/com/android/server/NetworkManagementService.java5
-rw-r--r--services/core/java/com/android/server/StorageManagerService.java35
-rw-r--r--services/core/java/com/android/server/TelephonyRegistry.java4
-rw-r--r--services/core/java/com/android/server/am/ActiveServices.java135
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java55
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerShellCommand.java50
-rw-r--r--services/core/java/com/android/server/am/BroadcastConstants.java22
-rw-r--r--services/core/java/com/android/server/am/BroadcastLoopers.java37
-rw-r--r--services/core/java/com/android/server/am/BroadcastProcessQueue.java97
-rw-r--r--services/core/java/com/android/server/am/BroadcastQueueModernImpl.java133
-rw-r--r--services/core/java/com/android/server/am/ProcessList.java2
-rw-r--r--services/core/java/com/android/server/am/SameProcessApplicationThread.java18
-rw-r--r--services/core/java/com/android/server/am/ServiceRecord.java35
-rw-r--r--services/core/java/com/android/server/am/TEST_MAPPING28
-rw-r--r--services/core/java/com/android/server/am/UserController.java126
-rw-r--r--services/core/java/com/android/server/app/GameManagerService.java52
-rw-r--r--services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java13
-rw-r--r--services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java11
-rw-r--r--services/core/java/com/android/server/appop/AppOpsCheckingServiceTracingDecorator.java279
-rw-r--r--services/core/java/com/android/server/appop/AppOpsServiceImpl.java85
-rw-r--r--services/core/java/com/android/server/appop/OnOpModeChangedListener.java2
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java74
-rw-r--r--services/core/java/com/android/server/audio/PlaybackActivityMonitor.java63
-rw-r--r--services/core/java/com/android/server/audio/SoundEffectsHelper.java8
-rw-r--r--services/core/java/com/android/server/audio/TEST_MAPPING11
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/FaceService.java7
-rw-r--r--services/core/java/com/android/server/connectivity/Vpn.java5
-rw-r--r--services/core/java/com/android/server/content/SyncManager.java55
-rw-r--r--services/core/java/com/android/server/display/AutomaticBrightnessController.java43
-rw-r--r--services/core/java/com/android/server/display/BrightnessTracker.java89
-rw-r--r--services/core/java/com/android/server/display/DeviceStateToLayoutMap.java63
-rw-r--r--services/core/java/com/android/server/display/DisplayAdapter.java7
-rw-r--r--services/core/java/com/android/server/display/DisplayDeviceInfo.java19
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java27
-rw-r--r--services/core/java/com/android/server/display/DisplayModeDirector.java111
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerController.java80
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerController2.java94
-rw-r--r--services/core/java/com/android/server/display/LocalDisplayAdapter.java105
-rw-r--r--services/core/java/com/android/server/display/LogicalDisplay.java1
-rw-r--r--services/core/java/com/android/server/display/LogicalDisplayMapper.java16
-rw-r--r--services/core/java/com/android/server/display/OverlayDisplayAdapter.java1
-rw-r--r--services/core/java/com/android/server/display/PersistentDataStore.java12
-rw-r--r--services/core/java/com/android/server/display/VirtualDisplayAdapter.java16
-rw-r--r--services/core/java/com/android/server/display/WifiDisplayAdapter.java1
-rw-r--r--services/core/java/com/android/server/display/brightness/BrightnessReason.java9
-rw-r--r--services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java10
-rw-r--r--services/core/java/com/android/server/display/brightness/strategy/BoostBrightnessStrategy.java50
-rw-r--r--services/core/java/com/android/server/display/layout/DisplayIdProducer.java30
-rw-r--r--services/core/java/com/android/server/display/layout/Layout.java71
-rw-r--r--services/core/java/com/android/server/graphics/fonts/FontManagerService.java4
-rw-r--r--services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java5
-rw-r--r--services/core/java/com/android/server/hdmi/ArcTerminationActionFromAvr.java21
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java12
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiControlService.java24
-rw-r--r--services/core/java/com/android/server/input/InputManagerService.java54
-rw-r--r--services/core/java/com/android/server/input/KeyRemapper.java161
-rw-r--r--services/core/java/com/android/server/input/KeyboardLayoutManager.java34
-rw-r--r--services/core/java/com/android/server/input/NativeInputManagerService.java5
-rw-r--r--services/core/java/com/android/server/input/PersistentDataStore.java61
-rw-r--r--services/core/java/com/android/server/locales/LocaleManagerService.java9
-rw-r--r--services/core/java/com/android/server/location/contexthub/ContextHubService.java47
-rw-r--r--services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java26
-rw-r--r--services/core/java/com/android/server/locksettings/LockSettingsService.java106
-rw-r--r--services/core/java/com/android/server/locksettings/LockSettingsStorage.java2
-rw-r--r--services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java73
-rw-r--r--services/core/java/com/android/server/media/MediaSessionService.java5
-rw-r--r--services/core/java/com/android/server/notification/NotificationRecordLogger.java11
-rw-r--r--services/core/java/com/android/server/notification/SnoozeHelper.java23
-rw-r--r--services/core/java/com/android/server/om/OverlayManagerService.java10
-rw-r--r--services/core/java/com/android/server/om/OverlayManagerServiceImpl.java16
-rw-r--r--services/core/java/com/android/server/pm/AppStateHelper.java59
-rw-r--r--services/core/java/com/android/server/pm/InstallPackageHelper.java30
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerService.java9
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerLocal.java13
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java1
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerServiceUtils.java7
-rw-r--r--services/core/java/com/android/server/pm/PackageRemovedInfo.java1
-rw-r--r--services/core/java/com/android/server/pm/PackageSetting.java10
-rw-r--r--services/core/java/com/android/server/pm/Settings.java106
-rw-r--r--services/core/java/com/android/server/pm/ShortcutPackage.java8
-rw-r--r--services/core/java/com/android/server/pm/ShortcutService.java49
-rw-r--r--services/core/java/com/android/server/pm/UserManagerInternal.java18
-rw-r--r--services/core/java/com/android/server/pm/UserManagerService.java63
-rw-r--r--services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java4
-rw-r--r--services/core/java/com/android/server/pm/local/PackageManagerLocalImpl.java19
-rw-r--r--services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java2
-rw-r--r--services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java6
-rw-r--r--services/core/java/com/android/server/pm/permission/Permission.java4
-rw-r--r--services/core/java/com/android/server/pm/permission/PermissionManagerService.java6
-rw-r--r--services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java28
-rw-r--r--services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java9
-rw-r--r--services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java7
-rw-r--r--services/core/java/com/android/server/pm/pkg/PackageState.java37
-rw-r--r--services/core/java/com/android/server/pm/pkg/PackageStateImpl.java15
-rw-r--r--services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java166
-rw-r--r--services/core/java/com/android/server/pm/pkg/component/ParsedActivity.java4
-rw-r--r--services/core/java/com/android/server/pm/pkg/component/ParsedActivityImpl.java26
-rw-r--r--services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java14
-rw-r--r--services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java34
-rw-r--r--services/core/java/com/android/server/pm/snapshot/PackageDataSnapshot.java2
-rw-r--r--services/core/java/com/android/server/policy/DeviceStateProviderImpl.java7
-rw-r--r--services/core/java/com/android/server/policy/PermissionPolicyService.java2
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java76
-rw-r--r--services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java3
-rw-r--r--services/core/java/com/android/server/power/PowerGroup.java18
-rw-r--r--services/core/java/com/android/server/power/PowerManagerService.java89
-rw-r--r--services/core/java/com/android/server/power/stats/BatteryStatsImpl.java1
-rw-r--r--services/core/java/com/android/server/power/stats/MobileRadioPowerCalculator.java468
-rw-r--r--services/core/java/com/android/server/security/rkp/OWNERS1
-rw-r--r--services/core/java/com/android/server/security/rkp/RemoteProvisioningService.java126
-rw-r--r--services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java12
-rw-r--r--services/core/java/com/android/server/statusbar/StatusBarManagerService.java18
-rw-r--r--services/core/java/com/android/server/wallpaper/WallpaperManagerService.java16
-rw-r--r--services/core/java/com/android/server/wearable/WearableSensingManagerService.java29
-rw-r--r--services/core/java/com/android/server/wearable/WearableSensingShellCommand.java212
-rw-r--r--services/core/java/com/android/server/wm/ActivityMetricsLogger.java8
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java44
-rw-r--r--services/core/java/com/android/server/wm/ActivityServiceConnectionsHolder.java2
-rw-r--r--services/core/java/com/android/server/wm/ActivityStartController.java2
-rw-r--r--services/core/java/com/android/server/wm/ActivityStarter.java2
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerService.java7
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskSupervisor.java6
-rw-r--r--services/core/java/com/android/server/wm/AppTransitionController.java20
-rw-r--r--services/core/java/com/android/server/wm/BackNavigationController.java45
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java27
-rw-r--r--services/core/java/com/android/server/wm/DisplayPolicy.java19
-rw-r--r--services/core/java/com/android/server/wm/DisplayRotation.java33
-rw-r--r--services/core/java/com/android/server/wm/DisplayWindowPolicyControllerHelper.java6
-rw-r--r--services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java6
-rw-r--r--services/core/java/com/android/server/wm/KeyguardController.java28
-rw-r--r--services/core/java/com/android/server/wm/LetterboxUiController.java6
-rw-r--r--services/core/java/com/android/server/wm/RecentTasks.java2
-rw-r--r--services/core/java/com/android/server/wm/RecentsAnimation.java4
-rw-r--r--services/core/java/com/android/server/wm/RemoteAnimationController.java26
-rw-r--r--services/core/java/com/android/server/wm/RootWindowContainer.java5
-rw-r--r--services/core/java/com/android/server/wm/Task.java39
-rw-r--r--services/core/java/com/android/server/wm/TaskDisplayArea.java13
-rw-r--r--services/core/java/com/android/server/wm/TaskFragment.java99
-rw-r--r--services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java6
-rw-r--r--services/core/java/com/android/server/wm/TaskOrganizerController.java2
-rw-r--r--services/core/java/com/android/server/wm/TaskSnapshotController.java111
-rw-r--r--services/core/java/com/android/server/wm/Transition.java15
-rw-r--r--services/core/java/com/android/server/wm/TransitionController.java11
-rw-r--r--services/core/java/com/android/server/wm/WallpaperController.java4
-rw-r--r--services/core/java/com/android/server/wm/WallpaperWindowToken.java2
-rw-r--r--services/core/java/com/android/server/wm/WindowContainer.java67
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerShellCommand.java6
-rw-r--r--services/core/java/com/android/server/wm/WindowOrganizerController.java49
-rw-r--r--services/core/java/com/android/server/wm/WindowProcessController.java14
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java25
-rw-r--r--services/core/java/com/android/server/wm/utils/StateMachine.java280
-rw-r--r--services/core/java/com/android/server/wm/utils/TEST_MAPPING18
-rw-r--r--services/core/jni/Android.bp31
-rw-r--r--services/core/jni/com_android_server_input_InputManagerService.cpp7
-rw-r--r--services/core/jni/com_android_server_pm_Settings.cpp161
-rw-r--r--services/core/jni/com_android_server_tv_TvInputHal.cpp574
-rw-r--r--services/core/jni/onload.cpp2
-rw-r--r--services/core/jni/onload_settings.cpp39
-rw-r--r--services/core/jni/tvinput/BufferProducerThread.cpp147
-rw-r--r--services/core/jni/tvinput/BufferProducerThread.h56
-rw-r--r--services/core/jni/tvinput/JTvInputHal.cpp381
-rw-r--r--services/core/jni/tvinput/JTvInputHal.h185
-rw-r--r--services/core/jni/tvinput/OWNERS3
-rw-r--r--services/core/jni/tvinput/TvInputHal_hidl.cpp138
-rw-r--r--services/core/jni/tvinput/TvInputHal_hidl.h35
-rw-r--r--services/core/jni/tvinput/jstruct.h55
-rw-r--r--services/core/xsd/display-layout-config/display-layout-config.xsd1
-rw-r--r--services/core/xsd/display-layout-config/schema/current.txt2
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java38
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DeviceStateCacheImpl.java43
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/Owners.java70
-rw-r--r--services/java/com/android/server/SystemServer.java16
-rw-r--r--services/permission/Android.bp2
-rw-r--r--services/permission/java/com/android/server/permission/access/AccessCheckingService.kt35
-rw-r--r--services/permission/java/com/android/server/permission/access/AccessPersistence.kt8
-rw-r--r--services/permission/java/com/android/server/permission/access/AccessPolicy.kt96
-rw-r--r--services/permission/java/com/android/server/permission/access/AccessState.kt9
-rw-r--r--services/permission/java/com/android/server/permission/access/appop/AppOpsCheckingServiceCompatImpl.kt144
-rw-r--r--services/permission/java/com/android/server/permission/access/appop/BaseAppOpPolicy.kt23
-rw-r--r--services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt19
-rw-r--r--services/permission/java/com/android/server/permission/access/appop/UidAppOpPolicy.kt15
-rw-r--r--services/permission/java/com/android/server/permission/access/permission/ModernPermissionManagerServiceImpl.kt456
-rw-r--r--services/permission/java/com/android/server/permission/access/permission/UidPermissionPolicy.kt254
-rw-r--r--services/tests/InputMethodSystemServerTests/Android.bp51
-rw-r--r--services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidManifest.xml43
-rw-r--r--services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidTest.xml41
-rw-r--r--services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java276
-rw-r--r--services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/Android.bp62
-rw-r--r--services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/AndroidManifest.xml54
-rw-r--r--services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/res/drawable/key_border.xml30
-rw-r--r--services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/res/layout/input_view.xml22
-rw-r--r--services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/res/layout/qwerty_10_9_9.xml200
-rw-r--r--services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/res/values/dimens.xml24
-rw-r--r--services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/res/values/strings.xml20
-rw-r--r--services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/res/values/styles.xml66
-rw-r--r--services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/res/xml/method.xml23
-rw-r--r--services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/KeyCodeConstants.java68
-rw-r--r--services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/SimpleInputMethodService.java67
-rw-r--r--services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/SimpleKeyboard.java126
-rw-r--r--services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/ims/InputMethodServiceWrapper.java102
-rw-r--r--services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/testing/TestActivity.java105
-rw-r--r--services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/OverlayPathsUninstallSystemUpdatesTest.kt100
-rw-r--r--services/tests/PackageManagerServiceTests/host/test-apps/Generic/res/values/colors.xml19
-rw-r--r--services/tests/PackageManagerServiceTests/host/test-apps/Generic/res/values/overlayable.xml23
-rw-r--r--services/tests/PackageManagerServiceTests/server/Android.bp1
-rw-r--r--services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageManagerSettingsTests.java4
-rw-r--r--services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageParserTest.java10
-rw-r--r--services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/PackageManagerLocalSnapshotTest.kt30
-rw-r--r--services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt2
-rw-r--r--services/tests/mockingservicestests/assets/AppOpsUpgradeTest/appops-version-1.xml781
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java168
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java52
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java256
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java96
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java109
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java94
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceOrInternalTestCase.java213
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java169
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/power/ScreenUndimDetectorTest.java8
-rw-r--r--services/tests/servicestests/AndroidManifest.xml1
-rw-r--r--services/tests/servicestests/res/xml/power_profile_test_legacy_modem.xml32
-rw-r--r--services/tests/servicestests/res/xml/power_profile_test_modem_calculator.xml31
-rw-r--r--services/tests/servicestests/res/xml/power_profile_test_modem_calculator_multiactive.xml63
-rw-r--r--services/tests/servicestests/res/xml/power_profile_test_modem_default.xml35
-rw-r--r--services/tests/servicestests/src/com/android/server/NetworkManagementServiceTest.java368
-rw-r--r--services/tests/servicestests/src/com/android/server/am/UserControllerTest.java34
-rw-r--r--services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java257
-rw-r--r--services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java6
-rw-r--r--services/tests/servicestests/src/com/android/server/content/SyncManagerTest.java106
-rw-r--r--services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java3
-rw-r--r--services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java103
-rw-r--r--services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java197
-rw-r--r--services/tests/servicestests/src/com/android/server/display/DeviceStateToLayoutMapTest.java150
-rw-r--r--services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java218
-rw-r--r--services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java484
-rw-r--r--services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java38
-rw-r--r--services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java34
-rw-r--r--services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java19
-rw-r--r--services/tests/servicestests/src/com/android/server/display/brightness/strategy/BoostBrightnessStrategyTest.java64
-rw-r--r--services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java19
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java46
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java28
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java6
-rw-r--r--services/tests/servicestests/src/com/android/server/input/KeyRemapperTests.kt150
-rw-r--r--services/tests/servicestests/src/com/android/server/job/JobStoreTest.java194
-rw-r--r--services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java53
-rw-r--r--services/tests/servicestests/src/com/android/server/locksettings/FakeSettings.java60
-rw-r--r--services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java31
-rw-r--r--services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java8
-rw-r--r--services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java19
-rw-r--r--services/tests/servicestests/src/com/android/server/locksettings/LockscreenFrpTest.java5
-rw-r--r--services/tests/servicestests/src/com/android/server/locksettings/MockLockSettingsContext.java37
-rw-r--r--services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java59
-rw-r--r--services/tests/servicestests/src/com/android/server/locksettings/WeaverBasedSyntheticPasswordTests.java38
-rw-r--r--services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java15
-rw-r--r--services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java43
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java781
-rw-r--r--services/tests/servicestests/src/com/android/server/power/PowerGroupTest.java87
-rw-r--r--services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java35
-rw-r--r--services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsRule.java11
-rw-r--r--services/tests/servicestests/src/com/android/server/power/stats/MobileRadioPowerCalculatorTest.java409
-rw-r--r--services/tests/servicestests/test-apps/PackageParserApp/AndroidManifestApp4.xml2
-rw-r--r--services/tests/servicestests/test-apps/PackageParserApp/AndroidManifestApp6.xml2
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java31
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java12
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java80
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java14
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java50
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java4
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java96
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java4
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerTests.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java4
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java8
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java6
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java8
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java8
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java44
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java38
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TransitionTests.java128
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java4
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java12
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java20
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java4
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/utils/StateMachineTest.java237
-rw-r--r--services/usage/java/com/android/server/usage/UsageStatsService.java15
-rw-r--r--services/usb/java/com/android/server/usb/UsbDeviceManager.java4
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordAudioStreamCopier.java43
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java2
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/TrustedHotwordDetectorSession.java2
-rw-r--r--startop/view_compiler/TEST_MAPPING15
-rw-r--r--telephony/java/android/service/euicc/EuiccProfileInfo.java3
-rw-r--r--telephony/java/android/telephony/ServiceState.java6
-rw-r--r--telephony/java/android/telephony/SubscriptionInfo.java9
-rw-r--r--telephony/java/android/telephony/SubscriptionManager.java105
-rw-r--r--telephony/java/android/telephony/data/DataProfile.java2
-rw-r--r--telephony/java/android/telephony/euicc/EuiccCardManager.java2
-rw-r--r--telephony/java/android/telephony/euicc/EuiccNotification.java1
-rw-r--r--telephony/java/android/telephony/euicc/EuiccRulesAuthTable.java1
-rw-r--r--telephony/java/android/telephony/ims/feature/MmTelFeature.java1
-rw-r--r--telephony/java/com/android/internal/telephony/ISub.aidl4
-rw-r--r--tests/FlickerTests/AndroidTest.xml2
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt53
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt56
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/ActivityEmbeddingTestBase.kt4
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingPlaceholderSplit.kt36
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt18
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt21
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt18
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt8
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/helpers/AssistantAppHelper.kt41
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FixedOrientationAppHelper.kt8
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt5
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GameAppHelper.kt8
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt5
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt8
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeStateInitializeHelper.kt8
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/helpers/MailAppHelper.kt8
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NewTasksAppHelper.kt8
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NonResizeableAppHelper.kt8
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NotificationAppHelper.kt8
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt8
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SeamlessRotationAppHelper.kt8
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ShowWhenLockedAppHelper.kt8
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SimpleAppHelper.kt8
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt8
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt41
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt41
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeEditorPopupDialogTest.kt35
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt34
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt39
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/ime/CommonAssertions.kt12
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeAndDialogThemeAppTest.kt45
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt51
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowAndCloseTest.kt31
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowFromFixedOrientationAppTest.kt53
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt35
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTest.kt108
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt49
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt39
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest_ShellTransit.kt19
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt26
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest.kt19
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest_ShellTransit.kt7
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt37
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt21
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLauncherTransition.kt25
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt25
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWarm.kt25
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWithLockOverlayApp.kt27
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockTransition.kt27
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationCold.kt32
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarm.kt47
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt29
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt71
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt22
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt21
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt50
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt28
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt54
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest_ShellTransit.kt19
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt55
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest_ShellTransit.kt19
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt92
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt26
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt16
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt81
-rw-r--r--tests/HwAccelerationTest/AndroidManifest.xml8
-rw-r--r--tests/HwAccelerationTest/src/com/android/test/hwui/MeshActivity.java124
-rw-r--r--tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/ControllerActivity.java41
-rw-r--r--tests/utils/testutils/java/com/android/internal/util/test/BroadcastInterceptingContext.java6
-rw-r--r--tools/lint/common/src/main/java/com/google/android/lint/PermissionMethodUtils.kt10
-rw-r--r--tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt29
-rw-r--r--tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/SimpleManualPermissionEnforcementDetector.kt13
-rw-r--r--tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/SimpleManualPermissionEnforcementDetectorTest.kt244
-rw-r--r--tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/Stubs.kt6
-rw-r--r--wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java15
1800 files changed, 48327 insertions, 19787 deletions
diff --git a/Android.bp b/Android.bp
index cd55dcf999d8..3d0188a3918d 100644
--- a/Android.bp
+++ b/Android.bp
@@ -108,7 +108,7 @@ filegroup {
":android.security.legacykeystore-java-source",
":android.security.maintenance-java-source",
":android.security.metrics-java-source",
- ":android.system.keystore2-V1-java-source",
+ ":android.system.keystore2-V3-java-source",
":credstore_aidl",
":dumpstate_aidl",
":framework_native_aidl",
@@ -205,7 +205,7 @@ java_library {
"android.hardware.contexthub-V1.0-java",
"android.hardware.contexthub-V1.1-java",
"android.hardware.contexthub-V1.2-java",
- "android.hardware.contexthub-V1-java",
+ "android.hardware.contexthub-V2-java",
"android.hardware.gnss-V1.0-java",
"android.hardware.gnss-V2.1-java",
"android.hardware.health-V1.0-java-constants",
diff --git a/OWNERS b/OWNERS
index d4d19365f4d1..09a721f10c1b 100644
--- a/OWNERS
+++ b/OWNERS
@@ -15,6 +15,7 @@ nandana@google.com #{LAST_RESORT_SUGGESTION}
narayan@google.com #{LAST_RESORT_SUGGESTION}
ogunwale@google.com #{LAST_RESORT_SUGGESTION}
roosa@google.com #{LAST_RESORT_SUGGESTION}
+smoreland@google.com #{LAST_RESORT_SUGGESTION}
svetoslavganov@android.com #{LAST_RESORT_SUGGESTION}
svetoslavganov@google.com #{LAST_RESORT_SUGGESTION}
yamasani@google.com #{LAST_RESORT_SUGGESTION}
diff --git a/StubLibraries.bp b/StubLibraries.bp
index 0e084968410f..272b4f6e36e6 100644
--- a/StubLibraries.bp
+++ b/StubLibraries.bp
@@ -328,10 +328,12 @@ java_library {
java_library {
name: "android_test_stubs_current",
- // Modules do not have test APIs, but we want to include their SystemApis, like we include
- // the SystemApi of framework-non-updatable-sources.
static_libs: [
- "all-modules-system-stubs",
+ // Updatable modules do not have test APIs, but we want to include their SystemApis, like we
+ // include the SystemApi of framework-non-updatable-sources.
+ "all-updatable-modules-system-stubs",
+ // Non-updatable modules on the other hand can have test APIs, so include their test-stubs.
+ "all-non-updatable-modules-test-stubs",
"android-non-updatable.stubs.test",
"private-stub-annotations-jar",
],
diff --git a/apct-tests/perftests/windowmanager/AndroidManifest.xml b/apct-tests/perftests/windowmanager/AndroidManifest.xml
index 95ede345fbee..532a0fcfc92b 100644
--- a/apct-tests/perftests/windowmanager/AndroidManifest.xml
+++ b/apct-tests/perftests/windowmanager/AndroidManifest.xml
@@ -17,6 +17,10 @@
package="com.android.perftests.wm">
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
+ <uses-permission android:name="android.permission.READ_LOGS" />
+ <!-- For perfetto trace files -->
+ <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application>
<uses-library android:name="android.test.runner" />
@@ -26,6 +30,9 @@
<action android:name="com.android.perftests.core.PERFTEST" />
</intent-filter>
</activity>
+
+ <activity android:name="android.wm.InTaskTransitionTest$TestActivity"
+ android:process=":test" />
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/apct-tests/perftests/windowmanager/src/android/wm/InTaskTransitionTest.java b/apct-tests/perftests/windowmanager/src/android/wm/InTaskTransitionTest.java
new file mode 100644
index 000000000000..2d2cf1c80e1e
--- /dev/null
+++ b/apct-tests/perftests/windowmanager/src/android/wm/InTaskTransitionTest.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.wm;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Looper;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.perftests.utils.ManualBenchmarkState;
+import android.perftests.utils.PerfManualStatusReporter;
+import android.perftests.utils.PerfTestActivity;
+import android.view.WindowManagerGlobal;
+
+import org.junit.Rule;
+import org.junit.Test;
+
+/** Measure the performance of warm launch activity in the same task. */
+public class InTaskTransitionTest extends WindowManagerPerfTestBase
+ implements RemoteCallback.OnResultListener {
+
+ private static final long TIMEOUT_MS = 5000;
+
+ @Rule
+ public final PerfManualStatusReporter mPerfStatusReporter = new PerfManualStatusReporter();
+
+ private final TransitionMetricsReader mMetricsReader = new TransitionMetricsReader();
+
+ @Test
+ @ManualBenchmarkState.ManualBenchmarkTest(
+ targetTestDurationNs = 20 * TIME_1_S_IN_NS,
+ statsReport = @ManualBenchmarkState.StatsReport(
+ flags = ManualBenchmarkState.StatsReport.FLAG_ITERATION
+ | ManualBenchmarkState.StatsReport.FLAG_MEAN
+ | ManualBenchmarkState.StatsReport.FLAG_MAX))
+ public void testStartActivityInSameTask() {
+ final Context context = getInstrumentation().getContext();
+ final Activity activity = getInstrumentation().startActivitySync(
+ new Intent(context, PerfTestActivity.class)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+ final Intent next = new Intent(context, TestActivity.class);
+ next.putExtra(TestActivity.CALLBACK, new RemoteCallback(this));
+
+ final ManualBenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ long measuredTimeNs = 0;
+
+ boolean readerStarted = false;
+ while (state.keepRunning(measuredTimeNs)) {
+ if (!readerStarted && !state.isWarmingUp()) {
+ mMetricsReader.setCheckpoint();
+ readerStarted = true;
+ }
+ final long startTime = SystemClock.elapsedRealtimeNanos();
+ activity.startActivity(next);
+ synchronized (mMetricsReader) {
+ try {
+ mMetricsReader.wait(TIMEOUT_MS);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ measuredTimeNs = SystemClock.elapsedRealtimeNanos() - startTime;
+ }
+
+ for (TransitionMetricsReader.TransitionMetrics metrics : mMetricsReader.getMetrics()) {
+ if (metrics.mTransitionDelayMs > 0) {
+ state.addExtraResult("transitionDelayMs", metrics.mTransitionDelayMs);
+ }
+ if (metrics.mWindowsDrawnDelayMs > 0) {
+ state.addExtraResult("windowsDrawnDelayMs", metrics.mWindowsDrawnDelayMs);
+ }
+ }
+ }
+
+ @Override
+ public void onResult(Bundle result) {
+ // The test activity is destroyed.
+ synchronized (mMetricsReader) {
+ mMetricsReader.notifyAll();
+ }
+ }
+
+ /** The test activity runs on a different process to trigger metrics logs. */
+ public static class TestActivity extends Activity implements Runnable {
+ static final String CALLBACK = "callback";
+
+ private RemoteCallback mCallback;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mCallback = getIntent().getParcelableExtra(CALLBACK, RemoteCallback.class);
+ if (mCallback != null) {
+ Looper.myLooper().getQueue().addIdleHandler(() -> {
+ new Thread(this).start();
+ return false;
+ });
+ }
+ }
+
+ @Override
+ public void run() {
+ // Wait until transition animation is finished and then finish self.
+ try {
+ WindowManagerGlobal.getWindowManagerService()
+ .syncInputTransactions(true /* waitForAnimations */);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ finish();
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ if (mCallback != null) {
+ getMainThreadHandler().post(() -> mCallback.sendResult(null));
+ }
+ }
+ }
+}
diff --git a/apct-tests/perftests/windowmanager/src/android/wm/WindowManagerPerfTestBase.java b/apct-tests/perftests/windowmanager/src/android/wm/WindowManagerPerfTestBase.java
index 4b1982f60092..aea0326ec9a2 100644
--- a/apct-tests/perftests/windowmanager/src/android/wm/WindowManagerPerfTestBase.java
+++ b/apct-tests/perftests/windowmanager/src/android/wm/WindowManagerPerfTestBase.java
@@ -18,10 +18,17 @@ package android.wm;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_DELAY_MS;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_WINDOWS_DRAWN_DELAY_MS;
+
import android.app.Activity;
import android.content.Intent;
+import android.metrics.LogMaker;
+import android.metrics.MetricsReader;
import android.perftests.utils.PerfTestActivity;
import android.perftests.utils.WindowPerfTestBase;
+import android.util.SparseArray;
import androidx.test.runner.lifecycle.ActivityLifecycleCallback;
import androidx.test.runner.lifecycle.ActivityLifecycleMonitorRegistry;
@@ -31,6 +38,7 @@ import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import java.io.File;
+import java.util.ArrayList;
import java.util.concurrent.TimeUnit;
public class WindowManagerPerfTestBase extends WindowPerfTestBase {
@@ -124,4 +132,42 @@ public class WindowManagerPerfTestBase extends WindowPerfTestBase {
}
}
}
+
+ static class TransitionMetricsReader {
+ final MetricsReader mMetricsReader = new MetricsReader();
+
+ static class TransitionMetrics {
+ int mTransitionDelayMs;
+ int mWindowsDrawnDelayMs;
+ }
+
+ TransitionMetrics[] getMetrics() {
+ mMetricsReader.read(0);
+ final ArrayList<LogMaker> logs = new ArrayList<>();
+ final LogMaker logTemplate = new LogMaker(APP_TRANSITION);
+ while (mMetricsReader.hasNext()) {
+ final LogMaker b = mMetricsReader.next();
+ if (logTemplate.isSubsetOf(b)) {
+ logs.add(b);
+ }
+ }
+
+ final TransitionMetrics[] infoArray = new TransitionMetrics[logs.size()];
+ for (int i = 0; i < infoArray.length; i++) {
+ final LogMaker log = logs.get(i);
+ final SparseArray<Object> data = log.getEntries();
+ final TransitionMetrics info = new TransitionMetrics();
+ infoArray[i] = info;
+ info.mTransitionDelayMs =
+ (int) data.get(APP_TRANSITION_DELAY_MS, -1);
+ info.mWindowsDrawnDelayMs =
+ (int) data.get(APP_TRANSITION_WINDOWS_DRAWN_DELAY_MS, -1);
+ }
+ return infoArray;
+ }
+
+ void setCheckpoint() {
+ mMetricsReader.checkpoint();
+ }
+ }
}
diff --git a/apex/jobscheduler/framework/java/android/app/AlarmManager.java b/apex/jobscheduler/framework/java/android/app/AlarmManager.java
index dade7c3d84a8..53e81c789d2b 100644
--- a/apex/jobscheduler/framework/java/android/app/AlarmManager.java
+++ b/apex/jobscheduler/framework/java/android/app/AlarmManager.java
@@ -27,7 +27,6 @@ import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.TestApi;
import android.compat.annotation.ChangeId;
-import android.compat.annotation.Disabled;
import android.compat.annotation.EnabledSince;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
@@ -285,11 +284,10 @@ public class AlarmManager {
* The permission {@link Manifest.permission#SCHEDULE_EXACT_ALARM} will be denied, unless the
* user explicitly allows it from Settings.
*
- * TODO (b/226439802): Either enable it in the next SDK or replace it with a better alternative.
* @hide
*/
@ChangeId
- @Disabled
+ @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
public static final long SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT = 226439802L;
@UnsupportedAppUsage
diff --git a/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java b/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java
index 652c49a28d59..4242cf843874 100644
--- a/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java
+++ b/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java
@@ -17,7 +17,9 @@
package android.app;
import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
import android.app.job.IJobScheduler;
+import android.app.job.IUserVisibleJobObserver;
import android.app.job.JobInfo;
import android.app.job.JobScheduler;
import android.app.job.JobSnapshot;
@@ -34,7 +36,7 @@ import java.util.List;
* Note android.app.job is the better package to put this class, but we can't move it there
* because that'd break robolectric. Grr.
*
- * @hide
+ * @hide
*/
public class JobSchedulerImpl extends JobScheduler {
IJobScheduler mBinder;
@@ -107,6 +109,15 @@ public class JobSchedulerImpl extends JobScheduler {
}
@Override
+ public int getPendingJobReason(int jobId) {
+ try {
+ return mBinder.getPendingJobReason(jobId);
+ } catch (RemoteException e) {
+ return PENDING_JOB_REASON_UNDEFINED;
+ }
+ }
+
+ @Override
public boolean canRunLongJobs() {
try {
return mBinder.canRunLongJobs(mContext.getOpPackageName());
@@ -141,4 +152,37 @@ public class JobSchedulerImpl extends JobScheduler {
return null;
}
}
+
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.MANAGE_ACTIVITY_TASKS,
+ android.Manifest.permission.INTERACT_ACROSS_USERS_FULL})
+ @Override
+ public void registerUserVisibleJobObserver(@NonNull IUserVisibleJobObserver observer) {
+ try {
+ mBinder.registerUserVisibleJobObserver(observer);
+ } catch (RemoteException e) {
+ }
+ }
+
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.MANAGE_ACTIVITY_TASKS,
+ android.Manifest.permission.INTERACT_ACROSS_USERS_FULL})
+ @Override
+ public void unregisterUserVisibleJobObserver(@NonNull IUserVisibleJobObserver observer) {
+ try {
+ mBinder.unregisterUserVisibleJobObserver(observer);
+ } catch (RemoteException e) {
+ }
+ }
+
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.MANAGE_ACTIVITY_TASKS,
+ android.Manifest.permission.INTERACT_ACROSS_USERS_FULL})
+ @Override
+ public void stopUserVisibleJobsForUser(@NonNull String packageName, int userId) {
+ try {
+ mBinder.stopUserVisibleJobsForUser(packageName, userId);
+ } catch (RemoteException e) {
+ }
+ }
}
diff --git a/apex/jobscheduler/framework/java/android/app/job/IJobCallback.aidl b/apex/jobscheduler/framework/java/android/app/job/IJobCallback.aidl
index a3390b75b8bf..96494ec28204 100644
--- a/apex/jobscheduler/framework/java/android/app/job/IJobCallback.aidl
+++ b/apex/jobscheduler/framework/java/android/app/job/IJobCallback.aidl
@@ -16,6 +16,7 @@
package android.app.job;
+import android.app.Notification;
import android.app.job.JobWorkItem;
/**
@@ -104,4 +105,17 @@ interface IJobCallback {
*/
void updateTransferredNetworkBytes(int jobId, in JobWorkItem item,
long transferredDownloadBytes, long transferredUploadBytes);
+ /**
+ * Provide JobScheduler with a notification to post and tie to this job's
+ * lifecycle.
+ * This is required for all user-initiated job and optional for other jobs.
+ *
+ * @param jobId Unique integer used to identify this job.
+ * @param notificationId The ID for this notification, as per
+ * {@link android.app.NotificationManager#notify(int, Notification)}.
+ * @param notification The notification to be displayed.
+ * @param jobEndNotificationPolicy The policy to apply to the notification when the job stops.
+ */
+ void setNotification(int jobId, int notificationId,
+ in Notification notification, int jobEndNotificationPolicy);
}
diff --git a/apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl b/apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl
index d2be32e219c9..c87a2aff7dde 100644
--- a/apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl
+++ b/apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl
@@ -16,6 +16,7 @@
package android.app.job;
+import android.app.job.IUserVisibleJobObserver;
import android.app.job.JobInfo;
import android.app.job.JobSnapshot;
import android.app.job.JobWorkItem;
@@ -33,8 +34,15 @@ interface IJobScheduler {
void cancelAll();
ParceledListSlice getAllPendingJobs();
JobInfo getPendingJob(int jobId);
+ int getPendingJobReason(int jobId);
boolean canRunLongJobs(String packageName);
boolean hasRunLongJobsPermission(String packageName, int userId);
List<JobInfo> getStartedJobs();
ParceledListSlice getAllJobSnapshots();
+ @EnforcePermission(allOf={"MANAGE_ACTIVITY_TASKS", "INTERACT_ACROSS_USERS_FULL"})
+ void registerUserVisibleJobObserver(in IUserVisibleJobObserver observer);
+ @EnforcePermission(allOf={"MANAGE_ACTIVITY_TASKS", "INTERACT_ACROSS_USERS_FULL"})
+ void unregisterUserVisibleJobObserver(in IUserVisibleJobObserver observer);
+ @EnforcePermission(allOf={"MANAGE_ACTIVITY_TASKS", "INTERACT_ACROSS_USERS_FULL"})
+ void stopUserVisibleJobsForUser(String packageName, int userId);
}
diff --git a/apex/jobscheduler/framework/java/android/app/job/IUserVisibleJobObserver.aidl b/apex/jobscheduler/framework/java/android/app/job/IUserVisibleJobObserver.aidl
new file mode 100644
index 000000000000..f65a47d643bc
--- /dev/null
+++ b/apex/jobscheduler/framework/java/android/app/job/IUserVisibleJobObserver.aidl
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.job;
+
+import android.app.job.UserVisibleJobSummary;
+
+/**
+ * IPC protocol to know about user-visible job activity.
+ *
+ * @hide
+ */
+oneway interface IUserVisibleJobObserver {
+ /**
+ * Notify the client of all changes to a user-visible jobs' state.
+ * @param summary A token/summary that uniquely identifies and details a single running job
+ * @param isRunning whether the job is currently running or not
+ */
+ void onUserVisibleJobStateChanged(in UserVisibleJobSummary summary, boolean isRunning);
+}
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
index ed72530d8608..0205430e707f 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
@@ -98,6 +98,12 @@ public class JobParameters implements Parcelable {
*/
public static final int INTERNAL_STOP_REASON_SUCCESSFUL_FINISH =
JobProtoEnums.INTERNAL_STOP_REASON_SUCCESSFUL_FINISH; // 10.
+ /**
+ * The user stopped the job via some UI (eg. Task Manager).
+ * @hide
+ */
+ public static final int INTERNAL_STOP_REASON_USER_UI_STOP =
+ JobProtoEnums.INTERNAL_STOP_REASON_USER_UI_STOP; // 11.
/**
* All the stop reason codes. This should be regarded as an immutable array at runtime.
@@ -121,6 +127,7 @@ public class JobParameters implements Parcelable {
INTERNAL_STOP_REASON_DATA_CLEARED,
INTERNAL_STOP_REASON_RTC_UPDATED,
INTERNAL_STOP_REASON_SUCCESSFUL_FINISH,
+ INTERNAL_STOP_REASON_USER_UI_STOP,
};
/**
@@ -141,6 +148,7 @@ public class JobParameters implements Parcelable {
case INTERNAL_STOP_REASON_DATA_CLEARED: return "data_cleared";
case INTERNAL_STOP_REASON_RTC_UPDATED: return "rtc_updated";
case INTERNAL_STOP_REASON_SUCCESSFUL_FINISH: return "successful_finish";
+ case INTERNAL_STOP_REASON_USER_UI_STOP: return "user_ui_stop";
default: return "unknown:" + reasonCode;
}
}
@@ -230,7 +238,7 @@ public class JobParameters implements Parcelable {
public static final int STOP_REASON_APP_STANDBY = 12;
/**
* The user stopped the job. This can happen either through force-stop, adb shell commands,
- * or uninstalling.
+ * uninstalling, or some other UI.
*/
public static final int STOP_REASON_USER = 13;
/** The system is doing some processing that requires stopping this job. */
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
index 76f71a2b9fe2..659db9f7b6e3 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
@@ -23,10 +23,14 @@ import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.UserIdInt;
+import android.app.ActivityManager;
+import android.app.usage.UsageStatsManager;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledAfter;
import android.content.ClipData;
import android.content.Context;
+import android.content.pm.PackageManager;
+import android.net.NetworkRequest;
import android.os.Build;
import android.os.Bundle;
import android.os.PersistableBundle;
@@ -133,6 +137,132 @@ public abstract class JobScheduler {
*/
public static final int RESULT_SUCCESS = 1;
+ /** The job doesn't exist. */
+ public static final int PENDING_JOB_REASON_INVALID_JOB_ID = -2;
+ /** The job is currently running and is therefore not pending. */
+ public static final int PENDING_JOB_REASON_EXECUTING = -1;
+ /**
+ * There is no known reason why the job is pending.
+ * If additional reasons are added on newer Android versions, the system may return this reason
+ * to apps whose target SDK is not high enough to expect that reason.
+ */
+ public static final int PENDING_JOB_REASON_UNDEFINED = 0;
+ /**
+ * The app is in a state that prevents the job from running
+ * (eg. the {@link JobService} component is disabled).
+ */
+ public static final int PENDING_JOB_REASON_APP = 1;
+ /**
+ * The current standby bucket prevents the job from running.
+ *
+ * @see UsageStatsManager#STANDBY_BUCKET_RESTRICTED
+ */
+ public static final int PENDING_JOB_REASON_APP_STANDBY = 2;
+ /**
+ * The app is restricted from running in the background.
+ *
+ * @see ActivityManager#isBackgroundRestricted()
+ * @see PackageManager#isInstantApp()
+ */
+ public static final int PENDING_JOB_REASON_BACKGROUND_RESTRICTION = 3;
+ /**
+ * The requested battery-not-low constraint is not satisfied.
+ *
+ * @see JobInfo.Builder#setRequiresBatteryNotLow(boolean)
+ */
+ public static final int PENDING_JOB_REASON_CONSTRAINT_BATTERY_NOT_LOW = 4;
+ /**
+ * The requested charging constraint is not satisfied.
+ *
+ * @see JobInfo.Builder#setRequiresCharging(boolean)
+ */
+ public static final int PENDING_JOB_REASON_CONSTRAINT_CHARGING = 5;
+ /**
+ * The requested connectivity constraint is not satisfied.
+ *
+ * @see JobInfo.Builder#setRequiredNetwork(NetworkRequest)
+ * @see JobInfo.Builder#setRequiredNetworkType(int)
+ */
+ public static final int PENDING_JOB_REASON_CONSTRAINT_CONNECTIVITY = 6;
+ /**
+ * The requested content trigger constraint is not satisfied.
+ *
+ * @see JobInfo.Builder#addTriggerContentUri(JobInfo.TriggerContentUri)
+ */
+ public static final int PENDING_JOB_REASON_CONSTRAINT_CONTENT_TRIGGER = 7;
+ /**
+ * The requested idle constraint is not satisfied.
+ *
+ * @see JobInfo.Builder#setRequiresDeviceIdle(boolean)
+ */
+ public static final int PENDING_JOB_REASON_CONSTRAINT_DEVICE_IDLE = 8;
+ /**
+ * The minimum latency has not transpired.
+ *
+ * @see JobInfo.Builder#setMinimumLatency(long)
+ */
+ public static final int PENDING_JOB_REASON_CONSTRAINT_MINIMUM_LATENCY = 9;
+ /**
+ * The system's estimate of when the app will be launched is far away enough to warrant delaying
+ * this job.
+ *
+ * @see JobInfo#isPrefetch()
+ * @see JobInfo.Builder#setPrefetch(boolean)
+ */
+ public static final int PENDING_JOB_REASON_CONSTRAINT_PREFETCH = 10;
+ /**
+ * The requested storage-not-low constraint is not satisfied.
+ *
+ * @see JobInfo.Builder#setRequiresStorageNotLow(boolean)
+ */
+ public static final int PENDING_JOB_REASON_CONSTRAINT_STORAGE_NOT_LOW = 11;
+ /**
+ * The job is being deferred due to the device state (eg. Doze, battery saver, memory usage,
+ * thermal status, etc.).
+ */
+ public static final int PENDING_JOB_REASON_DEVICE_STATE = 12;
+ /**
+ * JobScheduler thinks it can defer this job to a more optimal running time.
+ */
+ public static final int PENDING_JOB_REASON_JOB_SCHEDULER_OPTIMIZATION = 13;
+ /**
+ * The app has consumed all of its current quota.
+ *
+ * @see UsageStatsManager#getAppStandbyBucket()
+ * @see JobParameters#STOP_REASON_QUOTA
+ */
+ public static final int PENDING_JOB_REASON_QUOTA = 14;
+ /**
+ * JobScheduler is respecting one of the user's actions (eg. force stop or adb shell commands)
+ * to defer this job.
+ */
+ public static final int PENDING_JOB_REASON_USER = 15;
+
+ /** @hide */
+ @IntDef(prefix = {"PENDING_JOB_REASON_"}, value = {
+ PENDING_JOB_REASON_UNDEFINED,
+ PENDING_JOB_REASON_APP,
+ PENDING_JOB_REASON_APP_STANDBY,
+ PENDING_JOB_REASON_BACKGROUND_RESTRICTION,
+ PENDING_JOB_REASON_CONSTRAINT_BATTERY_NOT_LOW,
+ PENDING_JOB_REASON_CONSTRAINT_CHARGING,
+ PENDING_JOB_REASON_CONSTRAINT_CONNECTIVITY,
+ PENDING_JOB_REASON_CONSTRAINT_CONTENT_TRIGGER,
+ PENDING_JOB_REASON_CONSTRAINT_DEVICE_IDLE,
+ PENDING_JOB_REASON_CONSTRAINT_MINIMUM_LATENCY,
+ PENDING_JOB_REASON_CONSTRAINT_PREFETCH,
+ PENDING_JOB_REASON_CONSTRAINT_STORAGE_NOT_LOW,
+ PENDING_JOB_REASON_DEVICE_STATE,
+ PENDING_JOB_REASON_EXECUTING,
+ PENDING_JOB_REASON_INVALID_JOB_ID,
+ PENDING_JOB_REASON_JOB_SCHEDULER_OPTIMIZATION,
+ PENDING_JOB_REASON_QUOTA,
+ PENDING_JOB_REASON_USER,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PendingJobReason {
+ }
+
/**
* Schedule a job to be executed. Will replace any currently scheduled job with the same
* ID with the new information in the {@link JobInfo}. If a job with the given ID is currently
@@ -250,6 +380,15 @@ public abstract class JobScheduler {
public abstract @Nullable JobInfo getPendingJob(int jobId);
/**
+ * Returns a reason why the job is pending and not currently executing. If there are multiple
+ * reasons why a job may be pending, this will only return one of them.
+ */
+ @PendingJobReason
+ public int getPendingJobReason(int jobId) {
+ return PENDING_JOB_REASON_UNDEFINED;
+ }
+
+ /**
* Returns {@code true} if the calling app currently holds the
* {@link android.Manifest.permission#RUN_LONG_JOBS} permission, allowing it to run long jobs.
*/
@@ -283,4 +422,32 @@ public abstract class JobScheduler {
*/
@SuppressWarnings("HiddenAbstractMethod")
public abstract List<JobSnapshot> getAllJobSnapshots();
-} \ No newline at end of file
+
+ /**
+ * @hide
+ */
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.MANAGE_ACTIVITY_TASKS,
+ android.Manifest.permission.INTERACT_ACROSS_USERS_FULL})
+ @SuppressWarnings("HiddenAbstractMethod")
+ public abstract void registerUserVisibleJobObserver(@NonNull IUserVisibleJobObserver observer);
+
+ /**
+ * @hide
+ */
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.MANAGE_ACTIVITY_TASKS,
+ android.Manifest.permission.INTERACT_ACROSS_USERS_FULL})
+ @SuppressWarnings("HiddenAbstractMethod")
+ public abstract void unregisterUserVisibleJobObserver(
+ @NonNull IUserVisibleJobObserver observer);
+
+ /**
+ * @hide
+ */
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.MANAGE_ACTIVITY_TASKS,
+ android.Manifest.permission.INTERACT_ACROSS_USERS_FULL})
+ @SuppressWarnings("HiddenAbstractMethod")
+ public abstract void stopUserVisibleJobsForUser(@NonNull String packageName, int userId);
+}
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobService.java b/apex/jobscheduler/framework/java/android/app/job/JobService.java
index bad641cf8bf6..e88e979460b8 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobService.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobService.java
@@ -19,13 +19,18 @@ package android.app.job;
import static android.app.job.JobScheduler.THROW_ON_INVALID_DATA_TRANSFER_IMPLEMENTATION;
import android.annotation.BytesLong;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.Notification;
import android.app.Service;
import android.compat.Compatibility;
import android.content.Intent;
import android.os.IBinder;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* <p>Entry point for the callback from the {@link android.app.job.JobScheduler}.</p>
* <p>This is the base class that handles asynchronous requests that were previously scheduled. You
@@ -63,6 +68,32 @@ public abstract class JobService extends Service {
public static final String PERMISSION_BIND =
"android.permission.BIND_JOB_SERVICE";
+ /**
+ * Detach the notification supplied to
+ * {@link #setNotification(JobParameters, int, Notification, int)} when the job ends.
+ * The notification will remain shown even after JobScheduler stops the job.
+ *
+ * @hide
+ */
+ public static final int JOB_END_NOTIFICATION_POLICY_DETACH = 0;
+ /**
+ * Cancel and remove the notification supplied to
+ * {@link #setNotification(JobParameters, int, Notification, int)} when the job ends.
+ * The notification will be removed from the notification shade.
+ *
+ * @hide
+ */
+ public static final int JOB_END_NOTIFICATION_POLICY_REMOVE = 1;
+
+ /** @hide */
+ @IntDef(prefix = {"JOB_END_NOTIFICATION_POLICY_"}, value = {
+ JOB_END_NOTIFICATION_POLICY_DETACH,
+ JOB_END_NOTIFICATION_POLICY_REMOVE,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface JobEndNotificationPolicy {
+ }
+
private JobServiceEngine mEngine;
/** @hide */
@@ -84,9 +115,9 @@ public abstract class JobService extends Service {
public long getTransferredDownloadBytes(@NonNull JobParameters params,
@Nullable JobWorkItem item) {
if (item == null) {
- return JobService.this.getTransferredDownloadBytes();
+ return JobService.this.getTransferredDownloadBytes(params);
} else {
- return JobService.this.getTransferredDownloadBytes(item);
+ return JobService.this.getTransferredDownloadBytes(params, item);
}
}
@@ -95,9 +126,9 @@ public abstract class JobService extends Service {
public long getTransferredUploadBytes(@NonNull JobParameters params,
@Nullable JobWorkItem item) {
if (item == null) {
- return JobService.this.getTransferredUploadBytes();
+ return JobService.this.getTransferredUploadBytes(params);
} else {
- return JobService.this.getTransferredUploadBytes(item);
+ return JobService.this.getTransferredUploadBytes(params, item);
}
}
};
@@ -274,7 +305,7 @@ public abstract class JobService extends Service {
*/
// TODO(255371817): specify the actual time JS will wait for progress before requesting
@BytesLong
- public long getTransferredDownloadBytes() {
+ public long getTransferredDownloadBytes(@NonNull JobParameters params) {
if (Compatibility.isChangeEnabled(THROW_ON_INVALID_DATA_TRANSFER_IMPLEMENTATION)) {
// Regular jobs don't have to implement this and JobScheduler won't call this API for
// non-data transfer jobs.
@@ -298,7 +329,7 @@ public abstract class JobService extends Service {
*/
// TODO(255371817): specify the actual time JS will wait for progress before requesting
@BytesLong
- public long getTransferredUploadBytes() {
+ public long getTransferredUploadBytes(@NonNull JobParameters params) {
if (Compatibility.isChangeEnabled(THROW_ON_INVALID_DATA_TRANSFER_IMPLEMENTATION)) {
// Regular jobs don't have to implement this and JobScheduler won't call this API for
// non-data transfer jobs.
@@ -324,9 +355,10 @@ public abstract class JobService extends Service {
*/
// TODO(255371817): specify the actual time JS will wait for progress before requesting
@BytesLong
- public long getTransferredDownloadBytes(@NonNull JobWorkItem item) {
+ public long getTransferredDownloadBytes(@NonNull JobParameters params,
+ @NonNull JobWorkItem item) {
if (item == null) {
- return getTransferredDownloadBytes();
+ return getTransferredDownloadBytes(params);
}
if (Compatibility.isChangeEnabled(THROW_ON_INVALID_DATA_TRANSFER_IMPLEMENTATION)) {
// Regular jobs don't have to implement this and JobScheduler won't call this API for
@@ -353,9 +385,10 @@ public abstract class JobService extends Service {
*/
// TODO(255371817): specify the actual time JS will wait for progress before requesting
@BytesLong
- public long getTransferredUploadBytes(@NonNull JobWorkItem item) {
+ public long getTransferredUploadBytes(@NonNull JobParameters params,
+ @NonNull JobWorkItem item) {
if (item == null) {
- return getTransferredUploadBytes();
+ return getTransferredUploadBytes(params);
}
if (Compatibility.isChangeEnabled(THROW_ON_INVALID_DATA_TRANSFER_IMPLEMENTATION)) {
// Regular jobs don't have to implement this and JobScheduler won't call this API for
@@ -364,4 +397,37 @@ public abstract class JobService extends Service {
}
return 0;
}
+
+ /**
+ * Provide JobScheduler with a notification to post and tie to this job's lifecycle.
+ * This is required for all user-initiated jobs
+ * (scheduled via {link JobInfo.Builder#setUserInitiated(boolean)}) and optional for
+ * other jobs. If the app does not call this method for a required notification within
+ * 10 seconds after {@link #onStartJob(JobParameters)} is called,
+ * the system will trigger an ANR and stop this job.
+ *
+ * <p>
+ * Note that certain types of jobs
+ * (e.g. {@link JobInfo.Builder#setDataTransfer data transfer jobs}) may require the
+ * notification to have certain characteristics and their documentation will state
+ * any such requirements.
+ *
+ * <p>
+ * JobScheduler will not remember this notification after the job has finished running,
+ * so apps must call this every time the job is started (if required or desired).
+ *
+ * @param params The parameters identifying this job, as supplied to
+ * the job in the {@link #onStartJob(JobParameters)} callback.
+ * @param notificationId The ID for this notification, as per
+ * {@link android.app.NotificationManager#notify(int,
+ * Notification)}.
+ * @param notification The notification to be displayed.
+ * @param jobEndNotificationPolicy The policy to apply to the notification when the job stops.
+ * @hide
+ */
+ public final void setNotification(@NonNull JobParameters params, int notificationId,
+ @NonNull Notification notification,
+ @JobEndNotificationPolicy int jobEndNotificationPolicy) {
+ mEngine.setNotification(params, notificationId, notification, jobEndNotificationPolicy);
+ }
}
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobServiceEngine.java b/apex/jobscheduler/framework/java/android/app/job/JobServiceEngine.java
index 83296a63205a..53e452f68b11 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobServiceEngine.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobServiceEngine.java
@@ -21,6 +21,7 @@ import static android.app.job.JobScheduler.THROW_ON_INVALID_DATA_TRANSFER_IMPLEM
import android.annotation.BytesLong;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.Notification;
import android.app.Service;
import android.compat.Compatibility;
import android.content.Intent;
@@ -73,6 +74,8 @@ public abstract class JobServiceEngine {
private static final int MSG_UPDATE_TRANSFERRED_NETWORK_BYTES = 5;
/** Message that the client wants to update JobScheduler of the estimated transfer size. */
private static final int MSG_UPDATE_ESTIMATED_NETWORK_BYTES = 6;
+ /** Message that the client wants to give JobScheduler a notification to tie to the job. */
+ private static final int MSG_SET_NOTIFICATION = 7;
private final IJobService mBinder;
@@ -250,6 +253,24 @@ public abstract class JobServiceEngine {
args.recycle();
break;
}
+ case MSG_SET_NOTIFICATION: {
+ final SomeArgs args = (SomeArgs) msg.obj;
+ final JobParameters params = (JobParameters) args.arg1;
+ final Notification notification = (Notification) args.arg2;
+ IJobCallback callback = params.getCallback();
+ if (callback != null) {
+ try {
+ callback.setNotification(params.getJobId(),
+ args.argi1, notification, args.argi2);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error providing notification: binder has gone away.");
+ }
+ } else {
+ Log.e(TAG, "setNotification() called for a nonexistent job.");
+ }
+ args.recycle();
+ break;
+ }
default:
Log.e(TAG, "Unrecognised message received.");
break;
@@ -432,4 +453,27 @@ public abstract class JobServiceEngine {
args.argl2 = uploadBytes;
mHandler.obtainMessage(MSG_UPDATE_ESTIMATED_NETWORK_BYTES, args).sendToTarget();
}
-} \ No newline at end of file
+
+ /**
+ * Give JobScheduler a notification to tie to this job's lifecycle.
+ *
+ * @hide
+ * @see JobService#setNotification(JobParameters, int, Notification, int)
+ */
+ public void setNotification(@NonNull JobParameters params, int notificationId,
+ @NonNull Notification notification,
+ @JobService.JobEndNotificationPolicy int jobEndNotificationPolicy) {
+ if (params == null) {
+ throw new NullPointerException("params");
+ }
+ if (notification == null) {
+ throw new NullPointerException("notification");
+ }
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = params;
+ args.arg2 = notification;
+ args.argi1 = notificationId;
+ args.argi2 = jobEndNotificationPolicy;
+ mHandler.obtainMessage(MSG_SET_NOTIFICATION, args).sendToTarget();
+ }
+}
diff --git a/core/java/android/view/InsetsVisibilities.aidl b/apex/jobscheduler/framework/java/android/app/job/UserVisibleJobSummary.aidl
index bd573ea7bc35..5160b42e40dc 100644
--- a/core/java/android/view/InsetsVisibilities.aidl
+++ b/apex/jobscheduler/framework/java/android/app/job/UserVisibleJobSummary.aidl
@@ -1,11 +1,11 @@
/**
- * Copyright (c) 2021, The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,6 +14,6 @@
* limitations under the License.
*/
-package android.view;
+package android.app.job;
-parcelable InsetsVisibilities;
+parcelable UserVisibleJobSummary;
diff --git a/apex/jobscheduler/framework/java/android/app/job/UserVisibleJobSummary.java b/apex/jobscheduler/framework/java/android/app/job/UserVisibleJobSummary.java
new file mode 100644
index 000000000000..afcbe7d8eb3d
--- /dev/null
+++ b/apex/jobscheduler/framework/java/android/app/job/UserVisibleJobSummary.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.job;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Summary of a scheduled job that the user is meant to be aware of.
+ *
+ * @hide
+ */
+public class UserVisibleJobSummary implements Parcelable {
+ private final int mCallingUid;
+ private final int mSourceUserId;
+ @NonNull
+ private final String mSourcePackageName;
+ private final int mJobId;
+
+ public UserVisibleJobSummary(int callingUid, int sourceUserId,
+ @NonNull String sourcePackageName, int jobId) {
+ mCallingUid = callingUid;
+ mSourceUserId = sourceUserId;
+ mSourcePackageName = sourcePackageName;
+ mJobId = jobId;
+ }
+
+ protected UserVisibleJobSummary(Parcel in) {
+ mCallingUid = in.readInt();
+ mSourceUserId = in.readInt();
+ mSourcePackageName = in.readString();
+ mJobId = in.readInt();
+ }
+
+ public int getCallingUid() {
+ return mCallingUid;
+ }
+
+ public int getJobId() {
+ return mJobId;
+ }
+
+ public int getSourceUserId() {
+ return mSourceUserId;
+ }
+
+ public String getSourcePackageName() {
+ return mSourcePackageName;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof UserVisibleJobSummary)) return false;
+ UserVisibleJobSummary that = (UserVisibleJobSummary) o;
+ return mCallingUid == that.mCallingUid
+ && mSourceUserId == that.mSourceUserId
+ && mSourcePackageName.equals(that.mSourcePackageName)
+ && mJobId == that.mJobId;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 0;
+ result = 31 * result + mCallingUid;
+ result = 31 * result + mSourceUserId;
+ result = 31 * result + mSourcePackageName.hashCode();
+ result = 31 * result + mJobId;
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "UserVisibleJobSummary{"
+ + "callingUid=" + mCallingUid
+ + ", sourceUserId=" + mSourceUserId
+ + ", sourcePackageName='" + mSourcePackageName + "'"
+ + ", jobId=" + mJobId
+ + "}";
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mCallingUid);
+ dest.writeInt(mSourceUserId);
+ dest.writeString(mSourcePackageName);
+ dest.writeInt(mJobId);
+ }
+
+ public static final Creator<UserVisibleJobSummary> CREATOR =
+ new Creator<UserVisibleJobSummary>() {
+ @Override
+ public UserVisibleJobSummary createFromParcel(Parcel in) {
+ return new UserVisibleJobSummary(in);
+ }
+
+ @Override
+ public UserVisibleJobSummary[] newArray(int size) {
+ return new UserVisibleJobSummary[size];
+ }
+ };
+}
diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
index dcc6aa6a1d67..e2d302f46187 100644
--- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
+++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
@@ -4821,6 +4821,9 @@ public class DeviceIdleController extends SystemService
Binder.restoreCallingIdentity(token);
}
} else {
+ if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) {
+ return -1;
+ }
synchronized (this) {
for (int j=0; j<mPowerSaveWhitelistAppsExceptIdle.size(); j++) {
pw.print("system-excidle,");
@@ -4882,6 +4885,9 @@ public class DeviceIdleController extends SystemService
pw.println("[-r] requires a package name");
return -1;
} else {
+ if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) {
+ return -1;
+ }
dumpTempWhitelistSchedule(pw, false);
}
} else if ("except-idle-whitelist".equals(cmd)) {
@@ -4957,6 +4963,9 @@ public class DeviceIdleController extends SystemService
Binder.restoreCallingIdentity(token);
}
} else {
+ if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) {
+ return -1;
+ }
synchronized (this) {
for (int j = 0; j < mPowerSaveWhitelistApps.size(); j++) {
pw.print(mPowerSaveWhitelistApps.keyAt(j));
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
index f9dd0b379cb2..16201b29571e 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -1175,7 +1175,7 @@ class JobConcurrencyManager {
if (jobStatus != null && !jsc.isWithinExecutionGuaranteeTime()
&& restriction.isJobRestricted(jobStatus)) {
- jsc.cancelExecutingJobLocked(restriction.getReason(),
+ jsc.cancelExecutingJobLocked(restriction.getStopReason(),
restriction.getInternalReason(),
JobParameters.getInternalReasonCodeDescription(
restriction.getInternalReason()));
@@ -1184,6 +1184,22 @@ class JobConcurrencyManager {
}
@GuardedBy("mLock")
+ void stopUserVisibleJobsLocked(int userId, @NonNull String packageName,
+ @JobParameters.StopReason int reason, int internalReasonCode) {
+ for (int i = mActiveServices.size() - 1; i >= 0; --i) {
+ final JobServiceContext jsc = mActiveServices.get(i);
+ final JobStatus jobStatus = jsc.getRunningJobLocked();
+
+ if (jobStatus != null && userId == jobStatus.getSourceUserId()
+ && jobStatus.getSourcePackageName().equals(packageName)
+ && jobStatus.isUserVisibleJob()) {
+ jsc.cancelExecutingJobLocked(reason, internalReasonCode,
+ JobParameters.getInternalReasonCodeDescription(internalReasonCode));
+ }
+ }
+ }
+
+ @GuardedBy("mLock")
void stopNonReadyActiveJobsLocked() {
for (int i = 0; i < mActiveServices.size(); i++) {
JobServiceContext serviceContext = mActiveServices.get(i);
@@ -1208,7 +1224,7 @@ class JobConcurrencyManager {
final JobRestriction restriction = mService.checkIfRestricted(running);
if (restriction != null) {
final int internalReasonCode = restriction.getInternalReason();
- serviceContext.cancelExecutingJobLocked(restriction.getReason(),
+ serviceContext.cancelExecutingJobLocked(restriction.getStopReason(),
internalReasonCode,
"restricted due to "
+ JobParameters.getInternalReasonCodeDescription(
@@ -1324,6 +1340,7 @@ class JobConcurrencyManager {
mActivePkgStats.add(
jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(),
packageStats);
+ mService.resetPendingJobReasonCache(jobStatus);
}
if (mService.getPendingJobQueue().remove(jobStatus)) {
mService.mJobPackageTracker.noteNonpending(jobStatus);
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 ee08f85cbcb6..6f58c7ce626c 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -16,11 +16,14 @@
package com.android.server.job;
+import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
+import static android.Manifest.permission.MANAGE_ACTIVITY_TASKS;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER;
import static android.text.format.DateUtils.HOUR_IN_MILLIS;
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
+import android.annotation.EnforcePermission;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
@@ -31,6 +34,7 @@ import android.app.AppGlobals;
import android.app.IUidObserver;
import android.app.compat.CompatChanges;
import android.app.job.IJobScheduler;
+import android.app.job.IUserVisibleJobObserver;
import android.app.job.JobInfo;
import android.app.job.JobParameters;
import android.app.job.JobProtoEnums;
@@ -38,6 +42,7 @@ import android.app.job.JobScheduler;
import android.app.job.JobService;
import android.app.job.JobSnapshot;
import android.app.job.JobWorkItem;
+import android.app.job.UserVisibleJobSummary;
import android.app.usage.UsageStatsManager;
import android.app.usage.UsageStatsManagerInternal;
import android.compat.annotation.ChangeId;
@@ -68,6 +73,7 @@ import android.os.Looper;
import android.os.Message;
import android.os.ParcelFileDescriptor;
import android.os.Process;
+import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
@@ -248,6 +254,8 @@ public class JobSchedulerService extends com.android.server.SystemService
static final int MSG_UID_IDLE = 7;
static final int MSG_CHECK_CHANGED_JOB_LIST = 8;
static final int MSG_CHECK_MEDIA_EXEMPTION = 9;
+ static final int MSG_INFORM_OBSERVER_OF_ALL_USER_VISIBLE_JOBS = 10;
+ static final int MSG_INFORM_OBSERVERS_OF_USER_VISIBLE_JOB_CHANGE = 11;
/** List of controllers that will notify this service of updates to jobs. */
final List<StateController> mControllers;
@@ -258,6 +266,8 @@ public class JobSchedulerService extends com.android.server.SystemService
private final List<RestrictingController> mRestrictiveControllers;
/** Need direct access to this for testing. */
private final StorageController mStorageController;
+ /** Needed to get estimated transfer time. */
+ private final ConnectivityController mConnectivityController;
/** Need directly for sending uid state changes */
private final DeviceIdleJobsController mDeviceIdleJobsController;
/** Needed to get next estimated launch time. */
@@ -279,6 +289,9 @@ public class JobSchedulerService extends com.android.server.SystemService
@GuardedBy("mLock")
private final SparseArray<String> mCloudMediaProviderPackages = new SparseArray<>();
+ private final RemoteCallbackList<IUserVisibleJobObserver> mUserVisibleJobObservers =
+ new RemoteCallbackList<>();
+
private final CountQuotaTracker mQuotaTracker;
private static final String QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG = ".schedulePersisted()";
private static final String QUOTA_TRACKER_SCHEDULE_LOGGED =
@@ -363,6 +376,9 @@ public class JobSchedulerService extends com.android.server.SystemService
@GuardedBy("mLock")
private final ArraySet<JobStatus> mChangedJobList = new ArraySet<>();
+ @GuardedBy("mPendingJobReasonCache") // Use its own lock to avoid blocking JS processing
+ private final SparseArray<SparseIntArray> mPendingJobReasonCache = new SparseArray<>();
+
/**
* Named indices into standby bucket arrays, for clarity in referring to
* specific buckets' bookkeeping.
@@ -450,6 +466,13 @@ public class JobSchedulerService extends com.android.server.SystemService
case Constants.KEY_RUNTIME_MIN_GUARANTEE_MS:
case Constants.KEY_RUNTIME_MIN_EJ_GUARANTEE_MS:
case Constants.KEY_RUNTIME_MIN_HIGH_PRIORITY_GUARANTEE_MS:
+ case Constants.KEY_RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS:
+ case Constants.KEY_RUNTIME_DATA_TRANSFER_LIMIT_MS:
+ case Constants.KEY_RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS:
+ case Constants.KEY_RUNTIME_USER_INITIATED_LIMIT_MS:
+ case Constants.KEY_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR:
+ case Constants.KEY_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS:
+ case Constants.KEY_RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS:
if (!runtimeUpdated) {
mConstants.updateRuntimeConstantsLocked();
runtimeUpdated = true;
@@ -541,6 +564,21 @@ public class JobSchedulerService extends com.android.server.SystemService
private static final String KEY_RUNTIME_MIN_EJ_GUARANTEE_MS = "runtime_min_ej_guarantee_ms";
private static final String KEY_RUNTIME_MIN_HIGH_PRIORITY_GUARANTEE_MS =
"runtime_min_high_priority_guarantee_ms";
+ private static final String KEY_RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS =
+ "runtime_min_data_transfer_guarantee_ms";
+ private static final String KEY_RUNTIME_DATA_TRANSFER_LIMIT_MS =
+ "runtime_data_transfer_limit_ms";
+ private static final String KEY_RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS =
+ "runtime_min_user_initiated_guarantee_ms";
+ private static final String KEY_RUNTIME_USER_INITIATED_LIMIT_MS =
+ "runtime_user_initiated_limit_ms";
+ private static final String
+ KEY_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR =
+ "runtime_min_user_initiated_data_transfer_guarantee_buffer_factor";
+ private static final String KEY_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS =
+ "runtime_min_user_initiated_data_transfer_guarantee_ms";
+ private static final String KEY_RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS =
+ "runtime_user_initiated_data_transfer_limit_ms";
private static final String KEY_PERSIST_IN_SPLIT_FILES = "persist_in_split_files";
@@ -570,6 +608,20 @@ public class JobSchedulerService extends com.android.server.SystemService
public static final long DEFAULT_RUNTIME_MIN_EJ_GUARANTEE_MS = 3 * MINUTE_IN_MILLIS;
@VisibleForTesting
static final long DEFAULT_RUNTIME_MIN_HIGH_PRIORITY_GUARANTEE_MS = 5 * MINUTE_IN_MILLIS;
+ public static final long DEFAULT_RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS =
+ DEFAULT_RUNTIME_MIN_GUARANTEE_MS;
+ public static final long DEFAULT_RUNTIME_DATA_TRANSFER_LIMIT_MS =
+ DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS;
+ public static final long DEFAULT_RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS =
+ Math.max(10 * MINUTE_IN_MILLIS, DEFAULT_RUNTIME_MIN_GUARANTEE_MS);
+ public static final long DEFAULT_RUNTIME_USER_INITIATED_LIMIT_MS =
+ Math.max(60 * MINUTE_IN_MILLIS, DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS);
+ public static final float
+ DEFAULT_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR = 1.35f;
+ public static final long DEFAULT_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS =
+ Math.max(10 * MINUTE_IN_MILLIS, DEFAULT_RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS);
+ public static final long DEFAULT_RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS =
+ Math.max(Long.MAX_VALUE, DEFAULT_RUNTIME_USER_INITIATED_LIMIT_MS);
static final boolean DEFAULT_PERSIST_IN_SPLIT_FILES = true;
private static final boolean DEFAULT_USE_TARE_POLICY = false;
@@ -686,6 +738,49 @@ public class JobSchedulerService extends com.android.server.SystemService
DEFAULT_RUNTIME_MIN_HIGH_PRIORITY_GUARANTEE_MS;
/**
+ * The minimum amount of time we try to guarantee normal data transfer jobs will run for.
+ */
+ public long RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS =
+ DEFAULT_RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS;
+
+ /**
+ * The maximum amount of time we will let a normal data transfer job run for. This will only
+ * apply if there are no other limits that apply to the specific data transfer job.
+ */
+ public long RUNTIME_DATA_TRANSFER_LIMIT_MS = DEFAULT_RUNTIME_DATA_TRANSFER_LIMIT_MS;
+
+ /**
+ * The minimum amount of time we try to guarantee normal user-initiated jobs will run for.
+ */
+ public long RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS =
+ DEFAULT_RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS;
+
+ /**
+ * The maximum amount of time we will let a user-initiated job run for. This will only
+ * apply if there are no other limits that apply to the specific user-initiated job.
+ */
+ public long RUNTIME_USER_INITIATED_LIMIT_MS = DEFAULT_RUNTIME_USER_INITIATED_LIMIT_MS;
+
+ /**
+ * A factor to apply to estimated transfer durations for user-initiated data transfer jobs
+ * so that we give some extra time for unexpected situations. This will be at least 1 and
+ * so can just be multiplied with the original value to get the final value.
+ */
+ public float RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR =
+ DEFAULT_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR;
+
+ /**
+ * The minimum amount of time we try to guarantee user-initiated data transfer jobs
+ * will run for.
+ */
+ public long RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS =
+ DEFAULT_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS;
+
+ /** The maximum amount of time we will let a user-initiated data transfer job run for. */
+ public long RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS =
+ DEFAULT_RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS;
+
+ /**
* Whether to persist jobs in split files (by UID). If false, all persisted jobs will be
* saved in a single file.
*/
@@ -787,7 +882,14 @@ public class JobSchedulerService extends com.android.server.SystemService
DeviceConfig.NAMESPACE_JOB_SCHEDULER,
KEY_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
KEY_RUNTIME_MIN_GUARANTEE_MS, KEY_RUNTIME_MIN_EJ_GUARANTEE_MS,
- KEY_RUNTIME_MIN_HIGH_PRIORITY_GUARANTEE_MS);
+ KEY_RUNTIME_MIN_HIGH_PRIORITY_GUARANTEE_MS,
+ KEY_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR,
+ KEY_RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS,
+ KEY_RUNTIME_DATA_TRANSFER_LIMIT_MS,
+ KEY_RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS,
+ KEY_RUNTIME_USER_INITIATED_LIMIT_MS,
+ KEY_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS,
+ KEY_RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS);
// Make sure min runtime for regular jobs is at least 10 minutes.
RUNTIME_MIN_GUARANTEE_MS = Math.max(10 * MINUTE_IN_MILLIS,
@@ -805,6 +907,49 @@ public class JobSchedulerService extends com.android.server.SystemService
RUNTIME_FREE_QUOTA_MAX_LIMIT_MS = Math.max(RUNTIME_MIN_GUARANTEE_MS,
properties.getLong(KEY_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS));
+ // Make sure min runtime is at least as long as regular jobs.
+ RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS = Math.max(RUNTIME_MIN_GUARANTEE_MS,
+ properties.getLong(
+ KEY_RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS,
+ DEFAULT_RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS));
+ // Max limit should be at least the min guarantee AND the free quota.
+ RUNTIME_DATA_TRANSFER_LIMIT_MS = Math.max(RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ Math.max(RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS,
+ properties.getLong(
+ KEY_RUNTIME_DATA_TRANSFER_LIMIT_MS,
+ DEFAULT_RUNTIME_DATA_TRANSFER_LIMIT_MS)));
+ // Make sure min runtime is at least as long as regular jobs.
+ RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS = Math.max(RUNTIME_MIN_GUARANTEE_MS,
+ properties.getLong(
+ KEY_RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS,
+ DEFAULT_RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS));
+ // Max limit should be at least the min guarantee AND the free quota.
+ RUNTIME_USER_INITIATED_LIMIT_MS = Math.max(RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ Math.max(RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS,
+ properties.getLong(
+ KEY_RUNTIME_USER_INITIATED_LIMIT_MS,
+ DEFAULT_RUNTIME_USER_INITIATED_LIMIT_MS)));
+ // The buffer factor should be at least 1 (so we don't decrease the time).
+ RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR = Math.max(1,
+ properties.getFloat(
+ KEY_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR,
+ DEFAULT_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR
+ ));
+ // Make sure min runtime is at least as long as other user-initiated jobs.
+ RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS = Math.max(
+ RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS,
+ properties.getLong(
+ KEY_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS,
+ DEFAULT_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS));
+ // Data transfer requires RUN_LONG_JOBS permission, so the upper limit will be higher
+ // than other jobs.
+ // Max limit should be the min guarantee and the max of other user-initiated jobs.
+ RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS = Math.max(
+ RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS,
+ Math.max(RUNTIME_USER_INITIATED_LIMIT_MS,
+ properties.getLong(
+ KEY_RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS,
+ DEFAULT_RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS)));
}
private boolean updateTareSettingsLocked(boolean isTareEnabled) {
@@ -853,6 +998,20 @@ public class JobSchedulerService extends com.android.server.SystemService
RUNTIME_MIN_HIGH_PRIORITY_GUARANTEE_MS).println();
pw.print(KEY_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS, RUNTIME_FREE_QUOTA_MAX_LIMIT_MS)
.println();
+ pw.print(KEY_RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS,
+ RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS).println();
+ pw.print(KEY_RUNTIME_DATA_TRANSFER_LIMIT_MS,
+ RUNTIME_DATA_TRANSFER_LIMIT_MS).println();
+ pw.print(KEY_RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS,
+ RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS).println();
+ pw.print(KEY_RUNTIME_USER_INITIATED_LIMIT_MS,
+ RUNTIME_USER_INITIATED_LIMIT_MS).println();
+ pw.print(KEY_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR,
+ RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR).println();
+ pw.print(KEY_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS,
+ RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS).println();
+ pw.print(KEY_RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS,
+ RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS).println();
pw.print(KEY_PERSIST_IN_SPLIT_FILES, PERSIST_IN_SPLIT_FILES).println();
@@ -1357,6 +1516,134 @@ public class JobSchedulerService extends com.android.server.SystemService
}
}
+ @JobScheduler.PendingJobReason
+ private int getPendingJobReason(int uid, int jobId) {
+ int reason;
+ // Some apps may attempt to query this frequently, so cache the reason under a separate lock
+ // so that the rest of JS processing isn't negatively impacted.
+ synchronized (mPendingJobReasonCache) {
+ SparseIntArray jobIdToReason = mPendingJobReasonCache.get(uid);
+ if (jobIdToReason != null) {
+ reason = jobIdToReason.get(jobId, JobScheduler.PENDING_JOB_REASON_UNDEFINED);
+ if (reason != JobScheduler.PENDING_JOB_REASON_UNDEFINED) {
+ return reason;
+ }
+ }
+ }
+ synchronized (mLock) {
+ reason = getPendingJobReasonLocked(uid, jobId);
+ if (DEBUG) {
+ Slog.v(TAG, "getPendingJobReason(" + uid + "," + jobId + ")=" + reason);
+ }
+ }
+ synchronized (mPendingJobReasonCache) {
+ SparseIntArray jobIdToReason = mPendingJobReasonCache.get(uid);
+ if (jobIdToReason == null) {
+ jobIdToReason = new SparseIntArray();
+ mPendingJobReasonCache.put(uid, jobIdToReason);
+ }
+ jobIdToReason.put(jobId, reason);
+ }
+ return reason;
+ }
+
+ @JobScheduler.PendingJobReason
+ @GuardedBy("mLock")
+ private int getPendingJobReasonLocked(int uid, int jobId) {
+ // Very similar code to isReadyToBeExecutedLocked.
+
+ JobStatus job = mJobs.getJobByUidAndJobId(uid, jobId);
+ if (job == null) {
+ // Job doesn't exist.
+ return JobScheduler.PENDING_JOB_REASON_INVALID_JOB_ID;
+ }
+
+ if (isCurrentlyRunningLocked(job)) {
+ return JobScheduler.PENDING_JOB_REASON_EXECUTING;
+ }
+
+ final boolean jobReady = job.isReady();
+
+ if (DEBUG) {
+ Slog.v(TAG, "getPendingJobReasonLocked: " + job.toShortString()
+ + " ready=" + jobReady);
+ }
+
+ if (!jobReady) {
+ return job.getPendingJobReason();
+ }
+
+ final boolean userStarted = areUsersStartedLocked(job);
+
+ if (DEBUG) {
+ Slog.v(TAG, "getPendingJobReasonLocked: " + job.toShortString()
+ + " userStarted=" + userStarted);
+ }
+ if (!userStarted) {
+ return JobScheduler.PENDING_JOB_REASON_USER;
+ }
+
+ final boolean backingUp = mBackingUpUids.get(job.getSourceUid());
+ if (DEBUG) {
+ Slog.v(TAG, "getPendingJobReasonLocked: " + job.toShortString()
+ + " backingUp=" + backingUp);
+ }
+
+ if (backingUp) {
+ // TODO: Should we make a special reason for this?
+ return JobScheduler.PENDING_JOB_REASON_APP;
+ }
+
+ JobRestriction restriction = checkIfRestricted(job);
+ if (DEBUG) {
+ Slog.v(TAG, "getPendingJobReasonLocked: " + job.toShortString()
+ + " restriction=" + restriction);
+ }
+ if (restriction != null) {
+ return restriction.getPendingReason();
+ }
+
+ // The following can be a little more expensive (especially jobActive, since we need to
+ // go through the array of all potentially active jobs), so we are doing them
+ // later... but still before checking with the package manager!
+ final boolean jobPending = mPendingJobQueue.contains(job);
+
+
+ if (DEBUG) {
+ Slog.v(TAG, "getPendingJobReasonLocked: " + job.toShortString()
+ + " pending=" + jobPending);
+ }
+
+ if (jobPending) {
+ // We haven't started the job for some reason. Presumably, there are too many jobs
+ // running.
+ return JobScheduler.PENDING_JOB_REASON_DEVICE_STATE;
+ }
+
+ final boolean jobActive = mConcurrencyManager.isJobRunningLocked(job);
+
+ if (DEBUG) {
+ Slog.v(TAG, "getPendingJobReasonLocked: " + job.toShortString()
+ + " active=" + jobActive);
+ }
+ if (jobActive) {
+ return JobScheduler.PENDING_JOB_REASON_UNDEFINED;
+ }
+
+ // Validate that the defined package+service is still present & viable.
+ final boolean componentUsable = isComponentUsable(job);
+
+ if (DEBUG) {
+ Slog.v(TAG, "getPendingJobReasonLocked: " + job.toShortString()
+ + " componentUsable=" + componentUsable);
+ }
+ if (!componentUsable) {
+ return JobScheduler.PENDING_JOB_REASON_APP;
+ }
+
+ return JobScheduler.PENDING_JOB_REASON_UNDEFINED;
+ }
+
public JobInfo getPendingJob(int uid, int jobId) {
synchronized (mLock) {
ArraySet<JobStatus> jobs = mJobs.getJobsByUid(uid);
@@ -1370,6 +1657,14 @@ public class JobSchedulerService extends com.android.server.SystemService
}
}
+ private void stopUserVisibleJobsInternal(@NonNull String packageName, int userId) {
+ synchronized (mLock) {
+ mConcurrencyManager.stopUserVisibleJobsLocked(userId, packageName,
+ JobParameters.STOP_REASON_USER,
+ JobParameters.INTERNAL_STOP_REASON_USER_UI_STOP);
+ }
+ }
+
private final Consumer<JobStatus> mCancelJobDueToUserRemovalConsumer = (toRemove) -> {
// There's no guarantee that the process has been stopped by the time we get
// here, but since this is a user-initiated action, it should be fine to just
@@ -1389,6 +1684,9 @@ public class JobSchedulerService extends com.android.server.SystemService
synchronized (mLock) {
mJobs.removeJobsOfUnlistedUsers(umi.getUserIds());
}
+ synchronized (mPendingJobReasonCache) {
+ mPendingJobReasonCache.clear();
+ }
}
private void cancelJobsForPackageAndUidLocked(String pkgName, int uid,
@@ -1705,9 +2003,9 @@ public class JobSchedulerService extends com.android.server.SystemService
final FlexibilityController flexibilityController =
new FlexibilityController(this, mPrefetchController);
mControllers.add(flexibilityController);
- final ConnectivityController connectivityController =
+ mConnectivityController =
new ConnectivityController(this, flexibilityController);
- mControllers.add(connectivityController);
+ mControllers.add(mConnectivityController);
mControllers.add(new TimeController(this));
final IdleController idleController = new IdleController(this, flexibilityController);
mControllers.add(idleController);
@@ -1723,16 +2021,16 @@ public class JobSchedulerService extends com.android.server.SystemService
mDeviceIdleJobsController = new DeviceIdleJobsController(this);
mControllers.add(mDeviceIdleJobsController);
mQuotaController =
- new QuotaController(this, backgroundJobsController, connectivityController);
+ new QuotaController(this, backgroundJobsController, mConnectivityController);
mControllers.add(mQuotaController);
mControllers.add(new ComponentController(this));
mTareController =
- new TareController(this, backgroundJobsController, connectivityController);
+ new TareController(this, backgroundJobsController, mConnectivityController);
mControllers.add(mTareController);
mRestrictiveControllers = new ArrayList<>();
mRestrictiveControllers.add(batteryController);
- mRestrictiveControllers.add(connectivityController);
+ mRestrictiveControllers.add(mConnectivityController);
mRestrictiveControllers.add(idleController);
// Create restrictions
@@ -1874,6 +2172,8 @@ public class JobSchedulerService extends com.android.server.SystemService
jobStatus.enqueueTime = sElapsedRealtimeClock.millis();
final boolean update = lastJob != null;
mJobs.add(jobStatus);
+ // Clear potentially cached INVALID_JOB_ID reason.
+ resetPendingJobReasonCache(jobStatus);
if (mReadyToRock) {
for (int i = 0; i < mControllers.size(); i++) {
StateController controller = mControllers.get(i);
@@ -1895,6 +2195,13 @@ public class JobSchedulerService extends com.android.server.SystemService
// Deal with any remaining work items in the old job.
jobStatus.stopTrackingJobLocked(incomingJob);
+ synchronized (mPendingJobReasonCache) {
+ SparseIntArray reasonCache = mPendingJobReasonCache.get(jobStatus.getUid());
+ if (reasonCache != null) {
+ reasonCache.delete(jobStatus.getJobId());
+ }
+ }
+
// Remove from store as well as controllers.
final boolean removed = mJobs.remove(jobStatus, removeFromPersisted);
if (!removed) {
@@ -1917,6 +2224,16 @@ public class JobSchedulerService extends com.android.server.SystemService
return removed;
}
+ /** Remove the pending job reason for this job from the cache. */
+ void resetPendingJobReasonCache(@NonNull JobStatus jobStatus) {
+ synchronized (mPendingJobReasonCache) {
+ final SparseIntArray reasons = mPendingJobReasonCache.get(jobStatus.getUid());
+ if (reasons != null) {
+ reasons.delete(jobStatus.getJobId());
+ }
+ }
+ }
+
/** Return {@code true} if the specified job is currently executing. */
@GuardedBy("mLock")
public boolean isCurrentlyRunningLocked(JobStatus job) {
@@ -2006,6 +2323,7 @@ public class JobSchedulerService extends com.android.server.SystemService
}
delayMillis =
Math.min(delayMillis, JobInfo.MAX_BACKOFF_DELAY_MILLIS);
+ // TODO(255767350): demote all jobs to regular for user stops so they don't keep privileges
JobStatus newJob = new JobStatus(failureToReschedule,
elapsedNowMillis + delayMillis,
JobStatus.NO_LATEST_RUNTIME, numFailures, numSystemStops,
@@ -2210,11 +2528,20 @@ public class JobSchedulerService extends com.android.server.SystemService
public void onControllerStateChanged(@Nullable ArraySet<JobStatus> changedJobs) {
if (changedJobs == null) {
mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
+ synchronized (mPendingJobReasonCache) {
+ mPendingJobReasonCache.clear();
+ }
} else if (changedJobs.size() > 0) {
synchronized (mLock) {
mChangedJobList.addAll(changedJobs);
}
mHandler.obtainMessage(MSG_CHECK_CHANGED_JOB_LIST).sendToTarget();
+ synchronized (mPendingJobReasonCache) {
+ for (int i = changedJobs.size() - 1; i >= 0; --i) {
+ final JobStatus job = changedJobs.valueAt(i);
+ resetPendingJobReasonCache(job);
+ }
+ }
}
}
@@ -2347,6 +2674,52 @@ public class JobSchedulerService extends com.android.server.SystemService
args.recycle();
break;
}
+
+ case MSG_INFORM_OBSERVER_OF_ALL_USER_VISIBLE_JOBS: {
+ final IUserVisibleJobObserver observer =
+ (IUserVisibleJobObserver) message.obj;
+ synchronized (mLock) {
+ for (int i = mConcurrencyManager.mActiveServices.size() - 1; i >= 0;
+ --i) {
+ JobServiceContext context =
+ mConcurrencyManager.mActiveServices.get(i);
+ final JobStatus jobStatus = context.getRunningJobLocked();
+ if (jobStatus != null && jobStatus.isUserVisibleJob()) {
+ try {
+ observer.onUserVisibleJobStateChanged(
+ jobStatus.getUserVisibleJobSummary(),
+ /* isRunning */ true);
+ } catch (RemoteException e) {
+ // Will be unregistered automatically by
+ // RemoteCallbackList's dead-object tracking,
+ // so don't need to remove it here.
+ break;
+ }
+ }
+ }
+ }
+ break;
+ }
+
+ case MSG_INFORM_OBSERVERS_OF_USER_VISIBLE_JOB_CHANGE: {
+ final SomeArgs args = (SomeArgs) message.obj;
+ final JobServiceContext context = (JobServiceContext) args.arg1;
+ final JobStatus jobStatus = (JobStatus) args.arg2;
+ final UserVisibleJobSummary summary = jobStatus.getUserVisibleJobSummary();
+ final boolean isRunning = args.argi1 == 1;
+ for (int i = mUserVisibleJobObservers.beginBroadcast() - 1; i >= 0; --i) {
+ try {
+ mUserVisibleJobObservers.getBroadcastItem(i)
+ .onUserVisibleJobStateChanged(summary, isRunning);
+ } catch (RemoteException e) {
+ // Will be unregistered automatically by RemoteCallbackList's
+ // dead-object tracking, so nothing we need to do here.
+ }
+ }
+ mUserVisibleJobObservers.finishBroadcast();
+ args.recycle();
+ break;
+ }
}
maybeRunPendingJobsLocked();
}
@@ -2568,6 +2941,23 @@ public class JobSchedulerService extends com.android.server.SystemService
if (DEBUG) {
Slog.d(TAG, "maybeQueueReadyJobsForExecutionLocked: Not running anything.");
}
+ final int numRunnableJobs = runnableJobs.size();
+ if (numRunnableJobs > 0) {
+ synchronized (mPendingJobReasonCache) {
+ for (int i = 0; i < numRunnableJobs; ++i) {
+ final JobStatus job = runnableJobs.get(i);
+ SparseIntArray reasons = mPendingJobReasonCache.get(job.getUid());
+ if (reasons == null) {
+ reasons = new SparseIntArray();
+ mPendingJobReasonCache.put(job.getUid(), reasons);
+ }
+ // We're force batching these jobs, so consider it an optimization
+ // policy reason.
+ reasons.put(job.getJobId(),
+ JobScheduler.PENDING_JOB_REASON_JOB_SCHEDULER_OPTIMIZATION);
+ }
+ }
+ }
}
// Be ready for next time
@@ -2783,7 +3173,30 @@ public class JobSchedulerService extends com.android.server.SystemService
/** Returns the minimum amount of time we should let this job run before timing out. */
public long getMinJobExecutionGuaranteeMs(JobStatus job) {
synchronized (mLock) {
- if (job.shouldTreatAsExpeditedJob()) {
+ final boolean shouldTreatAsDataTransfer = job.getJob().isDataTransfer()
+ && checkRunLongJobsPermission(job.getSourceUid(), job.getSourcePackageName());
+ if (job.shouldTreatAsUserInitiated()) {
+ if (shouldTreatAsDataTransfer) {
+ final long estimatedTransferTimeMs =
+ mConnectivityController.getEstimatedTransferTimeMs(job);
+ if (estimatedTransferTimeMs == ConnectivityController.UNKNOWN_TIME) {
+ return mConstants.RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS;
+ }
+ // Try to give the job at least as much time as we think the transfer will take,
+ // but cap it at the maximum limit
+ final long factoredTransferTimeMs = (long) (estimatedTransferTimeMs
+ * mConstants
+ .RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR);
+ return Math.min(mConstants.RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS,
+ Math.max(factoredTransferTimeMs,
+ mConstants.RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS
+ ));
+ }
+ return mConstants.RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS;
+ } else if (shouldTreatAsDataTransfer) {
+ // For now, don't increase a bg data transfer's minimum guarantee.
+ return mConstants.RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS;
+ } else if (job.shouldTreatAsExpeditedJob()) {
// Don't guarantee RESTRICTED jobs more than 5 minutes.
return job.getEffectiveStandbyBucket() != RESTRICTED_INDEX
? mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS
@@ -2799,6 +3212,16 @@ public class JobSchedulerService extends com.android.server.SystemService
/** Returns the maximum amount of time this job could run for. */
public long getMaxJobExecutionTimeMs(JobStatus job) {
synchronized (mLock) {
+ final boolean shouldTreatAsDataTransfer = job.getJob().isDataTransfer()
+ && checkRunLongJobsPermission(job.getSourceUid(), job.getSourcePackageName());
+ if (job.shouldTreatAsUserInitiated()) {
+ if (shouldTreatAsDataTransfer) {
+ return mConstants.RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS;
+ }
+ return mConstants.RUNTIME_USER_INITIATED_LIMIT_MS;
+ } else if (shouldTreatAsDataTransfer) {
+ return mConstants.RUNTIME_DATA_TRANSFER_LIMIT_MS;
+ }
return Math.min(mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
mConstants.USE_TARE_POLICY
? mTareController.getMaxJobExecutionTimeMsLocked(job)
@@ -2843,6 +3266,16 @@ public class JobSchedulerService extends com.android.server.SystemService
return adjustJobBias(bias, job);
}
+ void informObserversOfUserVisibleJobChange(JobServiceContext context, JobStatus jobStatus,
+ boolean isRunning) {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = context;
+ args.arg2 = jobStatus;
+ args.argi1 = isRunning ? 1 : 0;
+ mHandler.obtainMessage(MSG_INFORM_OBSERVERS_OF_USER_VISIBLE_JOB_CHANGE, args)
+ .sendToTarget();
+ }
+
private final class BatteryStateTracker extends BroadcastReceiver {
/**
* Track whether we're "charging", where charging means that we're ready to commit to
@@ -3370,6 +3803,18 @@ public class JobSchedulerService extends com.android.server.SystemService
}
@Override
+ public int getPendingJobReason(int jobId) throws RemoteException {
+ final int uid = Binder.getCallingUid();
+
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ return JobSchedulerService.this.getPendingJobReason(uid, jobId);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override
public JobInfo getPendingJob(int jobId) throws RemoteException {
final int uid = Binder.getCallingUid();
@@ -3436,13 +3881,6 @@ public class JobSchedulerService extends com.android.server.SystemService
return checkRunLongJobsPermission(uid, packageName);
}
- private boolean checkRunLongJobsPermission(int packageUid, String packageName) {
- // Returns true if both the appop and permission are granted.
- return PermissionChecker.checkPermissionForPreflight(getContext(),
- android.Manifest.permission.RUN_LONG_JOBS, PermissionChecker.PID_UNKNOWN,
- packageUid, packageName) == PermissionChecker.PERMISSION_GRANTED;
- }
-
/**
* "dumpsys" infrastructure
*/
@@ -3553,6 +3991,38 @@ public class JobSchedulerService extends com.android.server.SystemService
return new ParceledListSlice<>(snapshots);
}
}
+
+ @Override
+ @EnforcePermission(allOf = {MANAGE_ACTIVITY_TASKS, INTERACT_ACROSS_USERS_FULL})
+ public void registerUserVisibleJobObserver(@NonNull IUserVisibleJobObserver observer) {
+ super.registerUserVisibleJobObserver_enforcePermission();
+ if (observer == null) {
+ throw new NullPointerException("observer");
+ }
+ mUserVisibleJobObservers.register(observer);
+ mHandler.obtainMessage(MSG_INFORM_OBSERVER_OF_ALL_USER_VISIBLE_JOBS, observer)
+ .sendToTarget();
+ }
+
+ @Override
+ @EnforcePermission(allOf = {MANAGE_ACTIVITY_TASKS, INTERACT_ACROSS_USERS_FULL})
+ public void unregisterUserVisibleJobObserver(@NonNull IUserVisibleJobObserver observer) {
+ super.unregisterUserVisibleJobObserver_enforcePermission();
+ if (observer == null) {
+ throw new NullPointerException("observer");
+ }
+ mUserVisibleJobObservers.unregister(observer);
+ }
+
+ @Override
+ @EnforcePermission(allOf = {MANAGE_ACTIVITY_TASKS, INTERACT_ACROSS_USERS_FULL})
+ public void stopUserVisibleJobsForUser(@NonNull String packageName, int userId) {
+ super.stopUserVisibleJobsForUser_enforcePermission();
+ if (packageName == null) {
+ throw new NullPointerException("packageName");
+ }
+ JobSchedulerService.this.stopUserVisibleJobsInternal(packageName, userId);
+ }
}
// Shell command infrastructure: run the given job immediately
@@ -3689,13 +4159,27 @@ public class JobSchedulerService extends com.android.server.SystemService
}
}
+ private boolean checkRunLongJobsPermission(int packageUid, String packageName) {
+ // Returns true if both the appop and permission are granted.
+ return PermissionChecker.checkPermissionForPreflight(getTestableContext(),
+ android.Manifest.permission.RUN_LONG_JOBS, PermissionChecker.PID_UNKNOWN,
+ packageUid, packageName) == PermissionChecker.PERMISSION_GRANTED;
+ }
+
+ @VisibleForTesting
+ protected ConnectivityController getConnectivityController() {
+ return mConnectivityController;
+ }
+
// Shell command infrastructure
int getJobState(PrintWriter pw, String pkgName, int userId, int jobId) {
try {
final int uid = AppGlobals.getPackageManager().getPackageUid(pkgName, 0,
userId != UserHandle.USER_ALL ? userId : UserHandle.USER_SYSTEM);
if (uid < 0) {
- pw.print("unknown("); pw.print(pkgName); pw.println(")");
+ pw.print("unknown(");
+ pw.print(pkgName);
+ pw.println(")");
return JobSchedulerShellCommand.CMD_ERR_NO_PACKAGE;
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index 9aa6b1c298ef..fead68e021db 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -17,6 +17,8 @@
package com.android.server.job;
import static android.app.job.JobInfo.getPriorityString;
+import static android.app.job.JobService.JOB_END_NOTIFICATION_POLICY_DETACH;
+import static android.app.job.JobService.JOB_END_NOTIFICATION_POLICY_REMOVE;
import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_NONE;
import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
@@ -24,6 +26,7 @@ import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
import android.annotation.BytesLong;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.Notification;
import android.app.job.IJobCallback;
import android.app.job.IJobService;
import android.app.job.JobInfo;
@@ -58,6 +61,7 @@ import com.android.internal.util.FrameworkStatsLog;
import com.android.server.EventLogTags;
import com.android.server.LocalServices;
import com.android.server.job.controllers.JobStatus;
+import com.android.server.notification.NotificationManagerInternal;
import com.android.server.tare.EconomicPolicy;
import com.android.server.tare.EconomyManagerInternal;
import com.android.server.tare.JobSchedulerEconomicPolicy;
@@ -117,6 +121,7 @@ public final class JobServiceContext implements ServiceConnection {
private final EconomyManagerInternal mEconomyManagerInternal;
private final JobPackageTracker mJobPackageTracker;
private final PowerManager mPowerManager;
+ private final NotificationManagerInternal mNotificationManagerInternal;
private PowerManager.WakeLock mWakeLock;
// Execution state.
@@ -169,6 +174,11 @@ public final class JobServiceContext implements ServiceConnection {
/** The absolute maximum amount of time the job can run */
private long mMaxExecutionTimeMillis;
+ private int mNotificationId;
+ private Notification mNotification;
+ private int mNotificationPid;
+ private int mNotificationJobStopPolicy;
+
/**
* The stop reason for a pending cancel. If there's not pending cancel, then the value should be
* {@link JobParameters#STOP_REASON_UNDEFINED}.
@@ -235,6 +245,12 @@ public final class JobServiceContext implements ServiceConnection {
long downloadBytes, long uploadBytes) {
doUpdateTransferredNetworkBytes(this, jobId, item, downloadBytes, uploadBytes);
}
+
+ @Override
+ public void setNotification(int jobId, int notificationId,
+ Notification notification, int jobEndNotificationPolicy) {
+ doSetNotification(this, jobId, notificationId, notification, jobEndNotificationPolicy);
+ }
}
JobServiceContext(JobSchedulerService service, JobConcurrencyManager concurrencyManager,
@@ -244,6 +260,7 @@ public final class JobServiceContext implements ServiceConnection {
mService = service;
mBatteryStats = batteryStats;
mEconomyManagerInternal = LocalServices.getService(EconomyManagerInternal.class);
+ mNotificationManagerInternal = LocalServices.getService(NotificationManagerInternal.class);
mJobPackageTracker = tracker;
mCallbackHandler = new JobServiceHandler(looper);
mJobConcurrencyManager = concurrencyManager;
@@ -593,6 +610,49 @@ public final class JobServiceContext implements ServiceConnection {
}
}
+ private void doSetNotification(JobCallback cb, int jodId, int notificationId,
+ Notification notification, int jobEndNotificationPolicy) {
+ final int callingPid = Binder.getCallingPid();
+ final int callingUid = Binder.getCallingUid();
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ if (!verifyCallerLocked(cb)) {
+ return;
+ }
+ if (callingUid != mRunningJob.getUid()) {
+ Slog.wtfStack(TAG, "Calling UID isn't the same as running job's UID...");
+ throw new SecurityException("Can't post notification on behalf of another app");
+ }
+ if (notification == null) {
+ throw new NullPointerException("notification");
+ }
+ if (notification.getSmallIcon() == null) {
+ throw new IllegalArgumentException("small icon required");
+ }
+ final String callingPkgName = mRunningJob.getServiceComponent().getPackageName();
+ if (null == mNotificationManagerInternal.getNotificationChannel(
+ callingPkgName, callingUid, notification.getChannelId())) {
+ throw new IllegalArgumentException("invalid notification channel");
+ }
+ if (jobEndNotificationPolicy != JOB_END_NOTIFICATION_POLICY_DETACH
+ && jobEndNotificationPolicy != JOB_END_NOTIFICATION_POLICY_REMOVE) {
+ throw new IllegalArgumentException("invalid job end notification policy");
+ }
+ // TODO(260848384): ensure apps can't cancel the notification for user-initiated job
+ mNotificationManagerInternal.enqueueNotification(
+ callingPkgName, callingPkgName, callingUid, callingPid, /* tag */ null,
+ notificationId, notification, UserHandle.getUserId(callingUid));
+ mNotificationId = notificationId;
+ mNotification = notification;
+ mNotificationPid = callingPid;
+ mNotificationJobStopPolicy = jobEndNotificationPolicy;
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
private void doUpdateTransferredNetworkBytes(JobCallback jobCallback, int jobId,
@Nullable JobWorkItem item, long downloadBytes, long uploadBytes) {
// TODO(255393346): Make sure apps call this appropriately and monitor for abuse
@@ -884,6 +944,9 @@ public final class JobServiceContext implements ServiceConnection {
return;
}
scheduleOpTimeOutLocked();
+ if (mRunningJob.isUserVisibleJob()) {
+ mService.informObserversOfUserVisibleJobChange(this, mRunningJob, true);
+ }
break;
default:
Slog.e(TAG, "Handling started job but job wasn't starting! Was "
@@ -1116,6 +1179,13 @@ public final class JobServiceContext implements ServiceConnection {
JobSchedulerEconomicPolicy.ACTION_JOB_TIMEOUT,
String.valueOf(mRunningJob.getJobId()));
}
+ if (mNotification != null
+ && mNotificationJobStopPolicy == JOB_END_NOTIFICATION_POLICY_REMOVE) {
+ final String callingPkgName = completedJob.getServiceComponent().getPackageName();
+ mNotificationManagerInternal.cancelNotification(
+ callingPkgName, callingPkgName, completedJob.getUid(), mNotificationPid,
+ /* tag */ null, mNotificationId, UserHandle.getUserId(completedJob.getUid()));
+ }
if (mWakeLock != null) {
mWakeLock.release();
}
@@ -1133,7 +1203,11 @@ public final class JobServiceContext implements ServiceConnection {
mPendingStopReason = JobParameters.STOP_REASON_UNDEFINED;
mPendingInternalStopReason = 0;
mPendingDebugStopReason = null;
+ mNotification = null;
removeOpTimeOutLocked();
+ if (completedJob.isUserVisibleJob()) {
+ mService.informObserversOfUserVisibleJobChange(this, completedJob, false);
+ }
mCompletedListener.onJobCompletedLocked(completedJob, internalStopReason, reschedule);
mJobConcurrencyManager.onJobCompletedLocked(this, completedJob, workType);
}
@@ -1157,6 +1231,7 @@ public final class JobServiceContext implements ServiceConnection {
private void scheduleOpTimeOutLocked() {
removeOpTimeOutLocked();
+ // TODO(260848384): enforce setNotification timeout for user-initiated jobs
final long timeoutMillis;
switch (mVerb) {
case VERB_EXECUTING:
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
index 145ac52ccc8a..a1153e315954 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
@@ -91,8 +91,11 @@ public final class JobStore {
/** Threshold to adjust how often we want to write to the db. */
private static final long JOB_PERSIST_DELAY = 2000L;
- private static final String JOB_FILE_SPLIT_PREFIX = "jobs_";
+ @VisibleForTesting
+ static final String JOB_FILE_SPLIT_PREFIX = "jobs_";
private static final int ALL_UIDS = -1;
+ @VisibleForTesting
+ static final int INVALID_UID = -2;
final Object mLock;
final Object mWriteScheduleLock; // used solely for invariants around write scheduling
@@ -529,6 +532,25 @@ public final class JobStore {
return values;
}
+ @VisibleForTesting
+ static int extractUidFromJobFileName(@NonNull File file) {
+ final String fileName = file.getName();
+ if (fileName.startsWith(JOB_FILE_SPLIT_PREFIX)) {
+ try {
+ final int subEnd = fileName.length() - 4; // -4 for ".xml"
+ final int uid = Integer.parseInt(
+ fileName.substring(JOB_FILE_SPLIT_PREFIX.length(), subEnd));
+ if (uid < 0) {
+ return INVALID_UID;
+ }
+ return uid;
+ } catch (Exception e) {
+ Slog.e(TAG, "Unexpected file name format", e);
+ }
+ }
+ return INVALID_UID;
+ }
+
/**
* Runnable that writes {@link #mJobSet} out to xml.
* NOTE: This Runnable locks on mLock
@@ -543,6 +565,42 @@ public final class JobStore {
private void prepare() {
mCopyAllJobs = !mUseSplitFiles || mPendingJobWriteUids.get(ALL_UIDS);
+ if (mUseSplitFiles) {
+ // Put the set of changed UIDs in the copy list so that we update each file,
+ // especially if we've dropped all jobs for that UID.
+ if (mPendingJobWriteUids.get(ALL_UIDS)) {
+ // ALL_UIDS is only used when we switch file splitting policy or for tests,
+ // so going through the file list here shouldn't be
+ // a large performance hit on user devices.
+
+ final File[] files;
+ try {
+ files = mJobFileDirectory.listFiles();
+ } catch (SecurityException e) {
+ Slog.wtf(TAG, "Not allowed to read job file directory", e);
+ return;
+ }
+ if (files == null) {
+ Slog.wtfStack(TAG, "Couldn't get job file list");
+ } else {
+ for (File file : files) {
+ final int uid = extractUidFromJobFileName(file);
+ if (uid != INVALID_UID) {
+ mJobStoreCopy.put(uid, new ArrayList<>());
+ }
+ }
+ }
+ } else {
+ for (int i = 0; i < mPendingJobWriteUids.size(); ++i) {
+ mJobStoreCopy.put(mPendingJobWriteUids.keyAt(i), new ArrayList<>());
+ }
+ }
+ } else {
+ // Single file mode.
+ // Put the catchall UID in the copy list so that we update the single file,
+ // especially if we've dropped all persisted jobs.
+ mJobStoreCopy.put(ALL_UIDS, new ArrayList<>());
+ }
}
@Override
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
index 16dd16727fa6..6166921d64b2 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
@@ -42,7 +42,6 @@ import android.telephony.TelephonyManager;
import android.text.format.DateUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
-import android.util.DataUnit;
import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.Pools;
@@ -82,6 +81,8 @@ public final class ConnectivityController extends RestrictingController implemen
private static final boolean DEBUG = JobSchedulerService.DEBUG
|| Log.isLoggable(TAG, Log.DEBUG);
+ public static final long UNKNOWN_TIME = -1L;
+
// The networking stack has a hard limit so we can't make this configurable.
private static final int MAX_NETWORK_CALLBACKS = 125;
/**
@@ -570,9 +571,8 @@ public final class ConnectivityController extends RestrictingController implemen
// If we don't know the bandwidth, all we can do is hope the job finishes the minimum
// chunk in time.
if (bandwidthDown > 0) {
- // Divide by 8 to convert bits to bytes.
- final long estimatedMillis = ((minimumChunkBytes * DateUtils.SECOND_IN_MILLIS)
- / (DataUnit.KIBIBYTES.toBytes(bandwidthDown) / 8));
+ final long estimatedMillis =
+ calculateTransferTimeMs(minimumChunkBytes, bandwidthDown);
if (estimatedMillis > maxJobExecutionTimeMs) {
// If we'd never finish the minimum chunk before the timeout, we'd be insane!
Slog.w(TAG, "Minimum chunk " + minimumChunkBytes + " bytes over "
@@ -585,9 +585,8 @@ public final class ConnectivityController extends RestrictingController implemen
final long bandwidthUp = capabilities.getLinkUpstreamBandwidthKbps();
// If we don't know the bandwidth, all we can do is hope the job finishes in time.
if (bandwidthUp > 0) {
- // Divide by 8 to convert bits to bytes.
- final long estimatedMillis = ((minimumChunkBytes * DateUtils.SECOND_IN_MILLIS)
- / (DataUnit.KIBIBYTES.toBytes(bandwidthUp) / 8));
+ final long estimatedMillis =
+ calculateTransferTimeMs(minimumChunkBytes, bandwidthUp);
if (estimatedMillis > maxJobExecutionTimeMs) {
// If we'd never finish the minimum chunk before the timeout, we'd be insane!
Slog.w(TAG, "Minimum chunk " + minimumChunkBytes + " bytes over " + bandwidthUp
@@ -615,9 +614,7 @@ public final class ConnectivityController extends RestrictingController implemen
final long bandwidth = capabilities.getLinkDownstreamBandwidthKbps();
// If we don't know the bandwidth, all we can do is hope the job finishes in time.
if (bandwidth > 0) {
- // Divide by 8 to convert bits to bytes.
- final long estimatedMillis = ((downloadBytes * DateUtils.SECOND_IN_MILLIS)
- / (DataUnit.KIBIBYTES.toBytes(bandwidth) / 8));
+ final long estimatedMillis = calculateTransferTimeMs(downloadBytes, bandwidth);
if (estimatedMillis > maxJobExecutionTimeMs) {
// If we'd never finish before the timeout, we'd be insane!
Slog.w(TAG, "Estimated " + downloadBytes + " download bytes over " + bandwidth
@@ -633,9 +630,7 @@ public final class ConnectivityController extends RestrictingController implemen
final long bandwidth = capabilities.getLinkUpstreamBandwidthKbps();
// If we don't know the bandwidth, all we can do is hope the job finishes in time.
if (bandwidth > 0) {
- // Divide by 8 to convert bits to bytes.
- final long estimatedMillis = ((uploadBytes * DateUtils.SECOND_IN_MILLIS)
- / (DataUnit.KIBIBYTES.toBytes(bandwidth) / 8));
+ final long estimatedMillis = calculateTransferTimeMs(uploadBytes, bandwidth);
if (estimatedMillis > maxJobExecutionTimeMs) {
// If we'd never finish before the timeout, we'd be insane!
Slog.w(TAG, "Estimated " + uploadBytes + " upload bytes over " + bandwidth
@@ -649,6 +644,48 @@ public final class ConnectivityController extends RestrictingController implemen
return false;
}
+ /**
+ * Return the estimated amount of time this job will be transferring data,
+ * based on the current network speed.
+ */
+ public long getEstimatedTransferTimeMs(JobStatus jobStatus) {
+ final long downloadBytes = jobStatus.getEstimatedNetworkDownloadBytes();
+ final long uploadBytes = jobStatus.getEstimatedNetworkUploadBytes();
+ if (downloadBytes == JobInfo.NETWORK_BYTES_UNKNOWN
+ && uploadBytes == JobInfo.NETWORK_BYTES_UNKNOWN) {
+ return UNKNOWN_TIME;
+ }
+ if (jobStatus.network == null) {
+ // This job doesn't have a network assigned.
+ return UNKNOWN_TIME;
+ }
+ NetworkCapabilities capabilities = getNetworkCapabilities(jobStatus.network);
+ if (capabilities == null) {
+ return UNKNOWN_TIME;
+ }
+ final long estimatedDownloadTimeMs = calculateTransferTimeMs(downloadBytes,
+ capabilities.getLinkDownstreamBandwidthKbps());
+ final long estimatedUploadTimeMs = calculateTransferTimeMs(uploadBytes,
+ capabilities.getLinkUpstreamBandwidthKbps());
+ if (estimatedDownloadTimeMs == UNKNOWN_TIME) {
+ return estimatedUploadTimeMs;
+ } else if (estimatedUploadTimeMs == UNKNOWN_TIME) {
+ return estimatedDownloadTimeMs;
+ }
+ return estimatedDownloadTimeMs + estimatedUploadTimeMs;
+ }
+
+ @VisibleForTesting
+ static long calculateTransferTimeMs(long transferBytes, long bandwidthKbps) {
+ if (transferBytes == JobInfo.NETWORK_BYTES_UNKNOWN || bandwidthKbps <= 0) {
+ return UNKNOWN_TIME;
+ }
+ return (transferBytes * DateUtils.SECOND_IN_MILLIS)
+ // Multiply by 1000 to convert kilobits to bits.
+ // Divide by 8 to convert bits to bytes.
+ / (bandwidthKbps * 1000 / 8);
+ }
+
private static boolean isCongestionDelayed(JobStatus jobStatus, Network network,
NetworkCapabilities capabilities, Constants constants) {
// If network is congested, and job is less than 50% through the
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 da20684aaefb..419127e6c6a9 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
@@ -29,10 +29,13 @@ import static com.android.server.job.controllers.FlexibilityController.NUM_SYSTE
import static com.android.server.job.controllers.FlexibilityController.SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS;
import android.annotation.ElapsedRealtimeLong;
+import android.annotation.NonNull;
import android.app.AppGlobals;
import android.app.job.JobInfo;
import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
import android.app.job.JobWorkItem;
+import android.app.job.UserVisibleJobSummary;
import android.content.ClipData;
import android.content.ComponentName;
import android.net.Network;
@@ -106,6 +109,13 @@ public final class JobStatus {
static final int CONSTRAINT_BACKGROUND_NOT_RESTRICTED = 1 << 22; // Implicit constraint
static final int CONSTRAINT_FLEXIBLE = 1 << 21; // Implicit constraint
+ private static final int IMPLICIT_CONSTRAINTS = 0
+ | CONSTRAINT_BACKGROUND_NOT_RESTRICTED
+ | CONSTRAINT_DEVICE_NOT_DOZING
+ | CONSTRAINT_FLEXIBLE
+ | CONSTRAINT_TARE_WEALTH
+ | CONSTRAINT_WITHIN_QUOTA;
+
// The following set of dynamic constraints are for specific use cases (as explained in their
// relative naming and comments). Right now, they apply different constraints, which is fine,
// but if in the future, we have overlapping dynamic constraint sets, removing one constraint
@@ -446,6 +456,12 @@ public final class JobStatus {
*/
private boolean mExpeditedTareApproved;
+ /**
+ * Summary describing this job. Lazily created in {@link #getUserVisibleJobSummary()}
+ * since not every job will need it.
+ */
+ private UserVisibleJobSummary mUserVisibleJobSummary;
+
/////// Booleans that track if a job is ready to run. They should be updated whenever dependent
/////// states change.
@@ -1329,6 +1345,36 @@ public final class JobStatus {
}
/**
+ * @return true if the job was scheduled as a user-initiated job and it hasn't been downgraded
+ * for any reason.
+ */
+ public boolean shouldTreatAsUserInitiated() {
+ // TODO(248386641): implement
+ return false;
+ }
+
+ /**
+ * Return a summary that uniquely identifies the underlying job.
+ */
+ @NonNull
+ public UserVisibleJobSummary getUserVisibleJobSummary() {
+ if (mUserVisibleJobSummary == null) {
+ mUserVisibleJobSummary = new UserVisibleJobSummary(
+ callingUid, getSourceUserId(), getSourcePackageName(), getJobId());
+ }
+ return mUserVisibleJobSummary;
+ }
+
+ /**
+ * @return true if this is a job whose execution should be made visible to the user.
+ */
+ public boolean isUserVisibleJob() {
+ // TODO(255767350): limit to user-initiated jobs
+ // Placeholder implementation until we have the code in
+ return shouldTreatAsExpeditedJob();
+ }
+
+ /**
* @return true if the job is exempted from Doze restrictions and therefore allowed to run
* in Doze.
*/
@@ -1613,6 +1659,101 @@ public final class JobStatus {
}
}
+ /**
+ * If {@link #isReady()} returns false, this will return a single reason why the job isn't
+ * ready. If {@link #isReady()} returns true, this will return
+ * {@link JobScheduler#PENDING_JOB_REASON_UNDEFINED}.
+ */
+ @JobScheduler.PendingJobReason
+ public int getPendingJobReason() {
+ final int unsatisfiedConstraints = ~satisfiedConstraints
+ & (requiredConstraints | mDynamicConstraints | IMPLICIT_CONSTRAINTS);
+ if ((CONSTRAINT_BACKGROUND_NOT_RESTRICTED & unsatisfiedConstraints) != 0) {
+ // The BACKGROUND_NOT_RESTRICTED constraint could be unsatisfied either because
+ // the app is background restricted, or because we're restricting background work
+ // in battery saver. Assume that background restriction is the reason apps that
+ // jobs are not ready, and battery saver otherwise.
+ // This has the benefit of being consistent for background restricted apps
+ // (they'll always get BACKGROUND_RESTRICTION) as the reason, regardless of
+ // battery saver state.
+ if (mIsUserBgRestricted) {
+ return JobScheduler.PENDING_JOB_REASON_BACKGROUND_RESTRICTION;
+ }
+ return JobScheduler.PENDING_JOB_REASON_DEVICE_STATE;
+ }
+ if ((CONSTRAINT_BATTERY_NOT_LOW & unsatisfiedConstraints) != 0) {
+ if ((CONSTRAINT_BATTERY_NOT_LOW & requiredConstraints) != 0) {
+ // The developer requested this constraint, so it makes sense to return the
+ // explicit constraint reason.
+ return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_BATTERY_NOT_LOW;
+ }
+ // Hard-coding right now since the current dynamic constraint sets don't overlap
+ // TODO: return based on active dynamic constraint sets when they start overlapping
+ return JobScheduler.PENDING_JOB_REASON_APP_STANDBY;
+ }
+ if ((CONSTRAINT_CHARGING & unsatisfiedConstraints) != 0) {
+ if ((CONSTRAINT_CHARGING & requiredConstraints) != 0) {
+ // The developer requested this constraint, so it makes sense to return the
+ // explicit constraint reason.
+ return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_CHARGING;
+ }
+ // Hard-coding right now since the current dynamic constraint sets don't overlap
+ // TODO: return based on active dynamic constraint sets when they start overlapping
+ return JobScheduler.PENDING_JOB_REASON_APP_STANDBY;
+ }
+ if ((CONSTRAINT_CONNECTIVITY & unsatisfiedConstraints) != 0) {
+ return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_CONNECTIVITY;
+ }
+ if ((CONSTRAINT_CONTENT_TRIGGER & unsatisfiedConstraints) != 0) {
+ return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_CONTENT_TRIGGER;
+ }
+ if ((CONSTRAINT_DEVICE_NOT_DOZING & unsatisfiedConstraints) != 0) {
+ return JobScheduler.PENDING_JOB_REASON_DEVICE_STATE;
+ }
+ if ((CONSTRAINT_FLEXIBLE & unsatisfiedConstraints) != 0) {
+ return JobScheduler.PENDING_JOB_REASON_JOB_SCHEDULER_OPTIMIZATION;
+ }
+ if ((CONSTRAINT_IDLE & unsatisfiedConstraints) != 0) {
+ if ((CONSTRAINT_IDLE & requiredConstraints) != 0) {
+ // The developer requested this constraint, so it makes sense to return the
+ // explicit constraint reason.
+ return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_DEVICE_IDLE;
+ }
+ // Hard-coding right now since the current dynamic constraint sets don't overlap
+ // TODO: return based on active dynamic constraint sets when they start overlapping
+ return JobScheduler.PENDING_JOB_REASON_APP_STANDBY;
+ }
+ if ((CONSTRAINT_PREFETCH & unsatisfiedConstraints) != 0) {
+ return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_PREFETCH;
+ }
+ if ((CONSTRAINT_STORAGE_NOT_LOW & unsatisfiedConstraints) != 0) {
+ return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_STORAGE_NOT_LOW;
+ }
+ if ((CONSTRAINT_TARE_WEALTH & unsatisfiedConstraints) != 0) {
+ return JobScheduler.PENDING_JOB_REASON_QUOTA;
+ }
+ if ((CONSTRAINT_TIMING_DELAY & unsatisfiedConstraints) != 0) {
+ return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_MINIMUM_LATENCY;
+ }
+ if ((CONSTRAINT_WITHIN_QUOTA & unsatisfiedConstraints) != 0) {
+ return JobScheduler.PENDING_JOB_REASON_QUOTA;
+ }
+
+ if (getEffectiveStandbyBucket() == NEVER_INDEX) {
+ Slog.wtf(TAG, "App in NEVER bucket querying pending job reason");
+ // The user hasn't officially launched this app.
+ return JobScheduler.PENDING_JOB_REASON_USER;
+ }
+ if (serviceProcessName != null) {
+ return JobScheduler.PENDING_JOB_REASON_APP;
+ }
+
+ if (!isReady()) {
+ Slog.wtf(TAG, "Unknown reason job isn't ready");
+ }
+ return JobScheduler.PENDING_JOB_REASON_UNDEFINED;
+ }
+
/** @return whether or not the @param constraint is satisfied */
public boolean isConstraintSatisfied(int constraint) {
return (satisfiedConstraints&constraint) != 0;
diff --git a/apex/jobscheduler/service/java/com/android/server/job/restrictions/JobRestriction.java b/apex/jobscheduler/service/java/com/android/server/job/restrictions/JobRestriction.java
index 4067541b4646..7aab67a00b1d 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/restrictions/JobRestriction.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/restrictions/JobRestriction.java
@@ -18,6 +18,7 @@ package com.android.server.job.restrictions;
import android.app.job.JobInfo;
import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
import android.util.IndentingPrintWriter;
import android.util.proto.ProtoOutputStream;
@@ -28,20 +29,23 @@ import com.android.server.job.controllers.JobStatus;
* Used by {@link JobSchedulerService} to impose additional restrictions regarding whether jobs
* should be scheduled or not based on the state of the system/device.
* Every restriction is associated with exactly one stop reason, which could be retrieved using
- * {@link #getReason()} (and the internal reason via {@link #getInternalReason()}).
+ * {@link #getStopReason()}, one pending reason (retrievable via {@link #getPendingReason()},
+ * (and the internal reason via {@link #getInternalReason()}).
* Note, that this is not taken into account for the jobs that have
* {@link JobInfo#BIAS_FOREGROUND_SERVICE} bias or higher.
*/
public abstract class JobRestriction {
final JobSchedulerService mService;
- private final int mReason;
+ private final int mStopReason;
+ private final int mPendingReason;
private final int mInternalReason;
- JobRestriction(JobSchedulerService service, @JobParameters.StopReason int reason,
- int internalReason) {
+ protected JobRestriction(JobSchedulerService service, @JobParameters.StopReason int stopReason,
+ @JobScheduler.PendingJobReason int pendingReason, int internalReason) {
mService = service;
- mReason = reason;
+ mPendingReason = pendingReason;
+ mStopReason = stopReason;
mInternalReason = internalReason;
}
@@ -70,10 +74,15 @@ public abstract class JobRestriction {
public void dumpConstants(ProtoOutputStream proto) {
}
- /** @return reason code for the Restriction. */
+ @JobScheduler.PendingJobReason
+ public final int getPendingReason() {
+ return mPendingReason;
+ }
+
+ /** @return stop reason code for the Restriction. */
@JobParameters.StopReason
- public final int getReason() {
- return mReason;
+ public final int getStopReason() {
+ return mStopReason;
}
public final int getInternalReason() {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java b/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java
index ca2fd60a07b2..830031e2f442 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java
@@ -18,6 +18,7 @@ package com.android.server.job.restrictions;
import android.app.job.JobInfo;
import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
import android.os.PowerManager;
import android.os.PowerManager.OnThermalStatusChangedListener;
import android.util.IndentingPrintWriter;
@@ -42,6 +43,7 @@ public class ThermalStatusRestriction extends JobRestriction {
public ThermalStatusRestriction(JobSchedulerService service) {
super(service, JobParameters.STOP_REASON_DEVICE_STATE,
+ JobScheduler.PENDING_JOB_REASON_DEVICE_STATE,
JobParameters.INTERNAL_STOP_REASON_DEVICE_THERMAL);
}
diff --git a/api/Android.bp b/api/Android.bp
index b0ce9afe147b..318748e20808 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -118,9 +118,11 @@ combined_apis {
],
system_server_classpath: [
"service-art",
+ "service-configinfrastructure",
"service-healthconnect",
"service-media-s",
"service-permission",
+ "service-rkp",
"service-sdksandbox",
],
}
diff --git a/api/api.go b/api/api.go
index ba0fdc18d23e..c91ff815395f 100644
--- a/api/api.go
+++ b/api/api.go
@@ -36,6 +36,8 @@ var core_libraries_modules = []string{art, conscrypt, i18n}
// built against module_current SDK). Instead they are directly statically
// linked into the all-framework-module-lib, which is building against hidden
// APIs.
+// In addition, the modules in this list are allowed to contribute to test APIs
+// stubs.
var non_updatable_modules = []string{virtualization}
// The intention behind this soong plugin is to generate a number of "merged"
@@ -246,9 +248,33 @@ func createMergedPublicStubs(ctx android.LoadHookContext, modules []string) {
}
func createMergedSystemStubs(ctx android.LoadHookContext, modules []string) {
+ // First create the all-updatable-modules-system-stubs
+ {
+ updatable_modules := removeAll(modules, non_updatable_modules)
+ props := libraryProps{}
+ props.Name = proptools.StringPtr("all-updatable-modules-system-stubs")
+ props.Static_libs = transformArray(updatable_modules, "", ".stubs.system")
+ props.Sdk_version = proptools.StringPtr("module_current")
+ props.Visibility = []string{"//frameworks/base"}
+ ctx.CreateModule(java.LibraryFactory, &props)
+ }
+ // Now merge all-updatable-modules-system-stubs and stubs from non-updatable modules
+ // into all-modules-system-stubs.
+ {
+ props := libraryProps{}
+ props.Name = proptools.StringPtr("all-modules-system-stubs")
+ props.Static_libs = transformArray(non_updatable_modules, "", ".stubs.system")
+ props.Static_libs = append(props.Static_libs, "all-updatable-modules-system-stubs")
+ props.Sdk_version = proptools.StringPtr("module_current")
+ props.Visibility = []string{"//frameworks/base"}
+ ctx.CreateModule(java.LibraryFactory, &props)
+ }
+}
+
+func createMergedTestStubsForNonUpdatableModules(ctx android.LoadHookContext) {
props := libraryProps{}
- props.Name = proptools.StringPtr("all-modules-system-stubs")
- props.Static_libs = transformArray(modules, "", ".stubs.system")
+ props.Name = proptools.StringPtr("all-non-updatable-modules-test-stubs")
+ props.Static_libs = transformArray(non_updatable_modules, "", ".stubs.test")
props.Sdk_version = proptools.StringPtr("module_current")
props.Visibility = []string{"//frameworks/base"}
ctx.CreateModule(java.LibraryFactory, &props)
@@ -360,6 +386,7 @@ func (a *CombinedApis) createInternalModules(ctx android.LoadHookContext) {
createMergedPublicStubs(ctx, bootclasspath)
createMergedSystemStubs(ctx, bootclasspath)
+ createMergedTestStubsForNonUpdatableModules(ctx)
createMergedFrameworkModuleLibStubs(ctx, bootclasspath)
createMergedFrameworkImpl(ctx, bootclasspath)
diff --git a/boot/preloaded-classes b/boot/preloaded-classes
index d8b348e9caf4..528ce867d10a 100644
--- a/boot/preloaded-classes
+++ b/boot/preloaded-classes
@@ -9358,8 +9358,8 @@ android.widget.inline.InlinePresentationSpec$1
android.widget.inline.InlinePresentationSpec$BaseBuilder
android.widget.inline.InlinePresentationSpec$Builder
android.widget.inline.InlinePresentationSpec
-android.window.BackEvent$1
-android.window.BackEvent
+android.window.BackMotionEvent$1
+android.window.BackMotionEvent
android.window.ClientWindowFrames$1
android.window.ClientWindowFrames
android.window.CompatOnBackInvokedCallback
diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp
index c4d90c6295aa..80512f7b3187 100644
--- a/cmds/bootanimation/BootAnimation.cpp
+++ b/cmds/bootanimation/BootAnimation.cpp
@@ -1112,6 +1112,11 @@ bool BootAnimation::parseAnimationDesc(Animation& animation) {
int nextReadPos;
+ if (strlen(l) == 0) {
+ s = ++endl;
+ continue;
+ }
+
int topLineNumbers = sscanf(l, "%d %d %d %d", &width, &height, &fps, &progress);
if (topLineNumbers == 3 || topLineNumbers == 4) {
// SLOGD("> w=%d, h=%d, fps=%d, progress=%d", width, height, fps, progress);
diff --git a/config/preloaded-classes b/config/preloaded-classes
index f750249f3408..fa60140ec068 100644
--- a/config/preloaded-classes
+++ b/config/preloaded-classes
@@ -9389,8 +9389,8 @@ android.widget.inline.InlinePresentationSpec$1
android.widget.inline.InlinePresentationSpec$BaseBuilder
android.widget.inline.InlinePresentationSpec$Builder
android.widget.inline.InlinePresentationSpec
-android.window.BackEvent$1
-android.window.BackEvent
+android.window.BackMotionEvent$1
+android.window.BackMotionEvent
android.window.ClientWindowFrames$1
android.window.ClientWindowFrames
android.window.CompatOnBackInvokedCallback
diff --git a/core/api/current.txt b/core/api/current.txt
index f6ec91b36c91..9a2c75fe490a 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -94,6 +94,7 @@ package android {
field public static final String FOREGROUND_SERVICE_CAMERA = "android.permission.FOREGROUND_SERVICE_CAMERA";
field public static final String FOREGROUND_SERVICE_CONNECTED_DEVICE = "android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE";
field public static final String FOREGROUND_SERVICE_DATA_SYNC = "android.permission.FOREGROUND_SERVICE_DATA_SYNC";
+ field public static final String FOREGROUND_SERVICE_FILE_MANAGEMENT = "android.permission.FOREGROUND_SERVICE_FILE_MANAGEMENT";
field public static final String FOREGROUND_SERVICE_HEALTH = "android.permission.FOREGROUND_SERVICE_HEALTH";
field public static final String FOREGROUND_SERVICE_LOCATION = "android.permission.FOREGROUND_SERVICE_LOCATION";
field public static final String FOREGROUND_SERVICE_MEDIA_PLAYBACK = "android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK";
@@ -1261,6 +1262,7 @@ package android {
field public static final int requireDeviceUnlock = 16843756; // 0x10103ec
field public static final int required = 16843406; // 0x101028e
field public static final int requiredAccountType = 16843734; // 0x10103d6
+ field public static final int requiredDisplayCategory;
field public static final int requiredFeature = 16844116; // 0x1010554
field public static final int requiredForAllUsers = 16843728; // 0x10103d0
field public static final int requiredNotFeature = 16844117; // 0x1010555
@@ -1508,7 +1510,6 @@ package android {
field public static final int targetCellWidth = 16844340; // 0x1010634
field public static final int targetClass = 16842799; // 0x101002f
field @Deprecated public static final int targetDescriptions = 16843680; // 0x10103a0
- field public static final int targetDisplayCategory;
field public static final int targetId = 16843740; // 0x10103dc
field public static final int targetName = 16843853; // 0x101044d
field public static final int targetPackage = 16842785; // 0x1010021
@@ -3116,6 +3117,7 @@ package android.accessibilityservice {
method public boolean onGesture(@NonNull android.accessibilityservice.AccessibilityGestureEvent);
method public abstract void onInterrupt();
method protected boolean onKeyEvent(android.view.KeyEvent);
+ method public void onMotionEvent(@NonNull android.view.MotionEvent);
method protected void onServiceConnected();
method public void onSystemActionsChanged();
method public final boolean performGlobalAction(int);
@@ -3273,6 +3275,7 @@ package android.accessibilityservice {
method @Deprecated public String getDescription();
method public String getId();
method public int getInteractiveUiTimeoutMillis();
+ method public int getMotionEventSources();
method public int getNonInteractiveUiTimeoutMillis();
method public android.content.pm.ResolveInfo getResolveInfo();
method public String getSettingsActivityName();
@@ -3282,6 +3285,7 @@ package android.accessibilityservice {
method @Nullable public CharSequence loadIntro(@NonNull android.content.pm.PackageManager);
method public CharSequence loadSummary(android.content.pm.PackageManager);
method public void setInteractiveUiTimeoutMillis(@IntRange(from=0) int);
+ method public void setMotionEventSources(int);
method public void setNonInteractiveUiTimeoutMillis(@IntRange(from=0) int);
method public void writeToParcel(android.os.Parcel, int);
field public static final int CAPABILITY_CAN_CONTROL_MAGNIFICATION = 16; // 0x10
@@ -7279,8 +7283,8 @@ package android.app {
method public android.content.Intent getCropAndSetWallpaperIntent(android.net.Uri);
method public int getDesiredMinimumHeight();
method public int getDesiredMinimumWidth();
- method @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) public android.graphics.drawable.Drawable getDrawable();
- method @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) public android.graphics.drawable.Drawable getFastDrawable();
+ method @Nullable @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) public android.graphics.drawable.Drawable getDrawable();
+ method @Nullable @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) public android.graphics.drawable.Drawable getFastDrawable();
method public static android.app.WallpaperManager getInstance(android.content.Context);
method @Nullable public android.app.WallpaperColors getWallpaperColors(int);
method @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) public android.os.ParcelFileDescriptor getWallpaperFile(int);
@@ -7290,8 +7294,8 @@ package android.app {
method public boolean hasResourceWallpaper(@RawRes int);
method public boolean isSetWallpaperAllowed();
method public boolean isWallpaperSupported();
- method public android.graphics.drawable.Drawable peekDrawable();
- method @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) public android.graphics.drawable.Drawable peekFastDrawable();
+ method @Nullable public android.graphics.drawable.Drawable peekDrawable();
+ method @Nullable @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) public android.graphics.drawable.Drawable peekFastDrawable();
method public void removeOnColorsChangedListener(@NonNull android.app.WallpaperManager.OnColorsChangedListener);
method public void sendWallpaperCommand(android.os.IBinder, String, int, int, int, android.os.Bundle);
method @RequiresPermission(android.Manifest.permission.SET_WALLPAPER) public void setBitmap(android.graphics.Bitmap) throws java.io.IOException;
@@ -7471,7 +7475,7 @@ package android.app.admin {
method public boolean getBluetoothContactSharingDisabled(@NonNull android.content.ComponentName);
method public boolean getCameraDisabled(@Nullable android.content.ComponentName);
method @Deprecated @Nullable public String getCertInstallerPackage(@NonNull android.content.ComponentName) throws java.lang.SecurityException;
- method @Nullable public java.util.Set<java.lang.String> getCrossProfileCalendarPackages(@NonNull android.content.ComponentName);
+ method @Deprecated @Nullable public java.util.Set<java.lang.String> getCrossProfileCalendarPackages(@NonNull android.content.ComponentName);
method public boolean getCrossProfileCallerIdDisabled(@NonNull android.content.ComponentName);
method public boolean getCrossProfileContactsSearchDisabled(@NonNull android.content.ComponentName);
method @NonNull public java.util.Set<java.lang.String> getCrossProfilePackages(@NonNull android.content.ComponentName);
@@ -7620,7 +7624,7 @@ package android.app.admin {
method @Deprecated public void setCertInstallerPackage(@NonNull android.content.ComponentName, @Nullable String) throws java.lang.SecurityException;
method public void setCommonCriteriaModeEnabled(@NonNull android.content.ComponentName, boolean);
method public void setConfiguredNetworksLockdownState(@NonNull android.content.ComponentName, boolean);
- method public void setCrossProfileCalendarPackages(@NonNull android.content.ComponentName, @Nullable java.util.Set<java.lang.String>);
+ method @Deprecated public void setCrossProfileCalendarPackages(@NonNull android.content.ComponentName, @Nullable java.util.Set<java.lang.String>);
method public void setCrossProfileCallerIdDisabled(@NonNull android.content.ComponentName, boolean);
method public void setCrossProfileContactsSearchDisabled(@NonNull android.content.ComponentName, boolean);
method public void setCrossProfilePackages(@NonNull android.content.ComponentName, @NonNull java.util.Set<java.lang.String>);
@@ -8481,7 +8485,26 @@ package android.app.job {
method public abstract int enqueue(@NonNull android.app.job.JobInfo, @NonNull android.app.job.JobWorkItem);
method @NonNull public abstract java.util.List<android.app.job.JobInfo> getAllPendingJobs();
method @Nullable public abstract android.app.job.JobInfo getPendingJob(int);
+ method public int getPendingJobReason(int);
method public abstract int schedule(@NonNull android.app.job.JobInfo);
+ field public static final int PENDING_JOB_REASON_APP = 1; // 0x1
+ field public static final int PENDING_JOB_REASON_APP_STANDBY = 2; // 0x2
+ field public static final int PENDING_JOB_REASON_BACKGROUND_RESTRICTION = 3; // 0x3
+ field public static final int PENDING_JOB_REASON_CONSTRAINT_BATTERY_NOT_LOW = 4; // 0x4
+ field public static final int PENDING_JOB_REASON_CONSTRAINT_CHARGING = 5; // 0x5
+ field public static final int PENDING_JOB_REASON_CONSTRAINT_CONNECTIVITY = 6; // 0x6
+ field public static final int PENDING_JOB_REASON_CONSTRAINT_CONTENT_TRIGGER = 7; // 0x7
+ field public static final int PENDING_JOB_REASON_CONSTRAINT_DEVICE_IDLE = 8; // 0x8
+ field public static final int PENDING_JOB_REASON_CONSTRAINT_MINIMUM_LATENCY = 9; // 0x9
+ field public static final int PENDING_JOB_REASON_CONSTRAINT_PREFETCH = 10; // 0xa
+ field public static final int PENDING_JOB_REASON_CONSTRAINT_STORAGE_NOT_LOW = 11; // 0xb
+ field public static final int PENDING_JOB_REASON_DEVICE_STATE = 12; // 0xc
+ field public static final int PENDING_JOB_REASON_EXECUTING = -1; // 0xffffffff
+ field public static final int PENDING_JOB_REASON_INVALID_JOB_ID = -2; // 0xfffffffe
+ field public static final int PENDING_JOB_REASON_JOB_SCHEDULER_OPTIMIZATION = 13; // 0xd
+ field public static final int PENDING_JOB_REASON_QUOTA = 14; // 0xe
+ field public static final int PENDING_JOB_REASON_UNDEFINED = 0; // 0x0
+ field public static final int PENDING_JOB_REASON_USER = 15; // 0xf
field public static final int RESULT_FAILURE = 0; // 0x0
field public static final int RESULT_SUCCESS = 1; // 0x1
}
@@ -10343,6 +10366,7 @@ package android.content {
field @Deprecated @RequiresPermission("android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS") public static final String ACTION_CLOSE_SYSTEM_DIALOGS = "android.intent.action.CLOSE_SYSTEM_DIALOGS";
field public static final String ACTION_CONFIGURATION_CHANGED = "android.intent.action.CONFIGURATION_CHANGED";
field public static final String ACTION_CREATE_DOCUMENT = "android.intent.action.CREATE_DOCUMENT";
+ field public static final String ACTION_CREATE_NOTE = "android.intent.action.CREATE_NOTE";
field public static final String ACTION_CREATE_REMINDER = "android.intent.action.CREATE_REMINDER";
field public static final String ACTION_CREATE_SHORTCUT = "android.intent.action.CREATE_SHORTCUT";
field public static final String ACTION_DATE_CHANGED = "android.intent.action.DATE_CHANGED";
@@ -10591,6 +10615,7 @@ package android.content {
field public static final String EXTRA_UID = "android.intent.extra.UID";
field public static final String EXTRA_USER = "android.intent.extra.USER";
field public static final String EXTRA_USER_INITIATED = "android.intent.extra.USER_INITIATED";
+ field public static final String EXTRA_USE_STYLUS_MODE = "android.intent.extra.USE_STYLUS_MODE";
field public static final int FILL_IN_ACTION = 1; // 0x1
field public static final int FILL_IN_CATEGORIES = 4; // 0x4
field public static final int FILL_IN_CLIP_DATA = 128; // 0x80
@@ -11201,10 +11226,10 @@ package android.content.pm {
field public String parentActivityName;
field public String permission;
field public int persistableMode;
+ field @Nullable public String requiredDisplayCategory;
field public int screenOrientation;
field public int softInputMode;
field public String targetActivity;
- field @Nullable public String targetDisplayCategory;
field public String taskAffinity;
field public int theme;
field public int uiOptions;
@@ -11665,7 +11690,7 @@ package android.content.pm {
public class PackageInstaller {
method public void abandonSession(int);
- method public void checkInstallConstraints(@NonNull java.util.List<java.lang.String>, @NonNull android.content.pm.PackageInstaller.InstallConstraints, @NonNull java.util.function.Consumer<android.content.pm.PackageInstaller.InstallConstraintsResult>);
+ method public void checkInstallConstraints(@NonNull java.util.List<java.lang.String>, @NonNull android.content.pm.PackageInstaller.InstallConstraints, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.content.pm.PackageInstaller.InstallConstraintsResult>);
method public int createSession(@NonNull android.content.pm.PackageInstaller.SessionParams) throws java.io.IOException;
method @Deprecated @Nullable public android.content.pm.PackageInstaller.SessionInfo getActiveStagedSession();
method @NonNull public java.util.List<android.content.pm.PackageInstaller.SessionInfo> getActiveStagedSessions();
@@ -12467,6 +12492,7 @@ package android.content.pm {
field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_CAMERA}, anyOf={android.Manifest.permission.CAMERA}, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_CAMERA = 64; // 0x40
field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE}, anyOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.CHANGE_NETWORK_STATE, android.Manifest.permission.CHANGE_WIFI_STATE, android.Manifest.permission.CHANGE_WIFI_MULTICAST_STATE, android.Manifest.permission.NFC, android.Manifest.permission.TRANSMIT_IR}, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE = 16; // 0x10
field @Deprecated @RequiresPermission(value=android.Manifest.permission.FOREGROUND_SERVICE_DATA_SYNC, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_DATA_SYNC = 1; // 0x1
+ field @RequiresPermission(android.Manifest.permission.FOREGROUND_SERVICE_FILE_MANAGEMENT) public static final int FOREGROUND_SERVICE_TYPE_FILE_MANAGEMENT = 4096; // 0x1000
field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_HEALTH}, anyOf={android.Manifest.permission.ACTIVITY_RECOGNITION, android.Manifest.permission.BODY_SENSORS, android.Manifest.permission.HIGH_SAMPLING_RATE_SENSORS}, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_HEALTH = 256; // 0x100
field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_LOCATION}, anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_LOCATION = 8; // 0x8
field public static final int FOREGROUND_SERVICE_TYPE_MANIFEST = -1; // 0xffffffff
@@ -12475,10 +12501,10 @@ package android.content.pm {
field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_MICROPHONE}, anyOf={android.Manifest.permission.CAPTURE_AUDIO_OUTPUT, android.Manifest.permission.RECORD_AUDIO}, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_MICROPHONE = 128; // 0x80
field @Deprecated public static final int FOREGROUND_SERVICE_TYPE_NONE = 0; // 0x0
field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_PHONE_CALL}, anyOf={android.Manifest.permission.MANAGE_OWN_CALLS}, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_PHONE_CALL = 4; // 0x4
- field @RequiresPermission(value=android.Manifest.permission.FOREGROUND_SERVICE_REMOTE_MESSAGING, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING = 512; // 0x200
+ field @RequiresPermission(android.Manifest.permission.FOREGROUND_SERVICE_REMOTE_MESSAGING) public static final int FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING = 512; // 0x200
field public static final int FOREGROUND_SERVICE_TYPE_SHORT_SERVICE = 2048; // 0x800
- field @RequiresPermission(value=android.Manifest.permission.FOREGROUND_SERVICE_SPECIAL_USE, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_SPECIAL_USE = 1073741824; // 0x40000000
- field @RequiresPermission(value=android.Manifest.permission.FOREGROUND_SERVICE_SYSTEM_EXEMPTED, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED = 1024; // 0x400
+ field @RequiresPermission(android.Manifest.permission.FOREGROUND_SERVICE_SPECIAL_USE) public static final int FOREGROUND_SERVICE_TYPE_SPECIAL_USE = 1073741824; // 0x40000000
+ field @RequiresPermission(android.Manifest.permission.FOREGROUND_SERVICE_SYSTEM_EXEMPTED) public static final int FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED = 1024; // 0x400
field public int flags;
field public String permission;
}
@@ -13018,6 +13044,14 @@ package android.content.res.loader {
package android.credentials {
+ public class ClearCredentialStateException extends java.lang.Exception {
+ ctor public ClearCredentialStateException(@NonNull String, @Nullable String);
+ ctor public ClearCredentialStateException(@NonNull String, @Nullable String, @Nullable Throwable);
+ ctor public ClearCredentialStateException(@NonNull String, @Nullable Throwable);
+ ctor public ClearCredentialStateException(@NonNull String);
+ field @NonNull public final String errorType;
+ }
+
public final class ClearCredentialStateRequest implements android.os.Parcelable {
ctor public ClearCredentialStateRequest(@NonNull android.os.Bundle);
method public int describeContents();
@@ -13026,6 +13060,14 @@ package android.credentials {
field @NonNull public static final android.os.Parcelable.Creator<android.credentials.ClearCredentialStateRequest> CREATOR;
}
+ public class CreateCredentialException extends java.lang.Exception {
+ ctor public CreateCredentialException(@NonNull String, @Nullable String);
+ ctor public CreateCredentialException(@NonNull String, @Nullable String, @Nullable Throwable);
+ ctor public CreateCredentialException(@NonNull String, @Nullable Throwable);
+ ctor public CreateCredentialException(@NonNull String);
+ field @NonNull public final String errorType;
+ }
+
public final class CreateCredentialRequest implements android.os.Parcelable {
ctor public CreateCredentialRequest(@NonNull String, @NonNull android.os.Bundle, @NonNull android.os.Bundle, boolean);
method public int describeContents();
@@ -13055,24 +13097,24 @@ package android.credentials {
}
public final class CredentialManager {
- method public void clearCredentialState(@NonNull android.credentials.ClearCredentialStateRequest, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.credentials.CredentialManagerException>);
- method public void executeCreateCredential(@NonNull android.credentials.CreateCredentialRequest, @NonNull android.app.Activity, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.credentials.CreateCredentialResponse,android.credentials.CredentialManagerException>);
- method public void executeGetCredential(@NonNull android.credentials.GetCredentialRequest, @NonNull android.app.Activity, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.credentials.GetCredentialResponse,android.credentials.CredentialManagerException>);
+ method public void clearCredentialState(@NonNull android.credentials.ClearCredentialStateRequest, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.credentials.ClearCredentialStateException>);
+ method public void executeCreateCredential(@NonNull android.credentials.CreateCredentialRequest, @NonNull android.app.Activity, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.credentials.CreateCredentialResponse,android.credentials.CreateCredentialException>);
+ method public void executeGetCredential(@NonNull android.credentials.GetCredentialRequest, @NonNull android.app.Activity, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.credentials.GetCredentialResponse,android.credentials.GetCredentialException>);
}
- public class CredentialManagerException extends java.lang.Exception {
- ctor public CredentialManagerException(int, @Nullable String);
- ctor public CredentialManagerException(int, @Nullable String, @Nullable Throwable);
- ctor public CredentialManagerException(int, @Nullable Throwable);
- ctor public CredentialManagerException(int);
- field public static final int ERROR_UNKNOWN = 0; // 0x0
- field public final int errorCode;
+ public class GetCredentialException extends java.lang.Exception {
+ ctor public GetCredentialException(@NonNull String, @Nullable String);
+ ctor public GetCredentialException(@NonNull String, @Nullable String, @Nullable Throwable);
+ ctor public GetCredentialException(@NonNull String, @Nullable Throwable);
+ ctor public GetCredentialException(@NonNull String);
+ field @NonNull public final String errorType;
}
public final class GetCredentialOption implements android.os.Parcelable {
- ctor public GetCredentialOption(@NonNull String, @NonNull android.os.Bundle, boolean);
+ ctor public GetCredentialOption(@NonNull String, @NonNull android.os.Bundle, @NonNull android.os.Bundle, boolean);
method public int describeContents();
- method @NonNull public android.os.Bundle getData();
+ method @NonNull public android.os.Bundle getCandidateQueryData();
+ method @NonNull public android.os.Bundle getCredentialRetrievalData();
method @NonNull public String getType();
method public boolean requireSystemProvider();
method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -17800,6 +17842,7 @@ package android.hardware.camera2 {
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> SENSOR_MAX_ANALOG_SENSITIVITY;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.graphics.Rect[]> SENSOR_OPTICAL_BLACK_REGIONS;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> SENSOR_ORIENTATION;
+ field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> SENSOR_READOUT_TIMESTAMP;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> SENSOR_REFERENCE_ILLUMINANT1;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Byte> SENSOR_REFERENCE_ILLUMINANT2;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> SHADING_AVAILABLE_MODES;
@@ -18168,6 +18211,8 @@ package android.hardware.camera2 {
field public static final int SENSOR_INFO_TIMESTAMP_SOURCE_UNKNOWN = 0; // 0x0
field public static final int SENSOR_PIXEL_MODE_DEFAULT = 0; // 0x0
field public static final int SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION = 1; // 0x1
+ field public static final int SENSOR_READOUT_TIMESTAMP_HARDWARE = 1; // 0x1
+ field public static final int SENSOR_READOUT_TIMESTAMP_NOT_SUPPORTED = 0; // 0x0
field public static final int SENSOR_REFERENCE_ILLUMINANT1_CLOUDY_WEATHER = 10; // 0xa
field public static final int SENSOR_REFERENCE_ILLUMINANT1_COOL_WHITE_FLUORESCENT = 14; // 0xe
field public static final int SENSOR_REFERENCE_ILLUMINANT1_D50 = 23; // 0x17
@@ -18281,6 +18326,7 @@ package android.hardware.camera2 {
field @NonNull public static final android.os.Parcelable.Creator<android.hardware.camera2.CaptureRequest> CREATOR;
field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> DISTORTION_CORRECTION_MODE;
field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> EDGE_MODE;
+ field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> EXTENSION_STRENGTH;
field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> FLASH_MODE;
field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> HOT_PIXEL_MODE;
field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<android.location.Location> JPEG_GPS_LOCATION;
@@ -18373,6 +18419,8 @@ package android.hardware.camera2 {
field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Float> CONTROL_ZOOM_RATIO;
field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> DISTORTION_CORRECTION_MODE;
field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> EDGE_MODE;
+ field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> EXTENSION_CURRENT_TYPE;
+ field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> EXTENSION_STRENGTH;
field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> FLASH_MODE;
field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> FLASH_STATE;
field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> HOT_PIXEL_MODE;
@@ -20176,6 +20224,15 @@ package android.location {
}
+package android.location.altitude {
+
+ public final class AltitudeConverter {
+ ctor public AltitudeConverter();
+ method @WorkerThread public void addMslAltitudeToLocation(@NonNull android.content.Context, @NonNull android.location.Location) throws java.io.IOException;
+ }
+
+}
+
package android.location.provider {
public final class ProviderProperties implements android.os.Parcelable {
@@ -22014,6 +22071,11 @@ package android.media {
field public static final int AVCProfileHigh422 = 32; // 0x20
field public static final int AVCProfileHigh444 = 64; // 0x40
field public static final int AVCProfileMain = 2; // 0x2
+ field public static final int DTS_HDProfileHRA = 1; // 0x1
+ field public static final int DTS_HDProfileLBR = 2; // 0x2
+ field public static final int DTS_HDProfileMA = 4; // 0x4
+ field public static final int DTS_UHDProfileP1 = 1; // 0x1
+ field public static final int DTS_UHDProfileP2 = 2; // 0x2
field public static final int DolbyVisionLevel8k30 = 1024; // 0x400
field public static final int DolbyVisionLevel8k60 = 2048; // 0x800
field public static final int DolbyVisionLevelFhd24 = 4; // 0x4
@@ -35961,7 +36023,7 @@ package android.provider {
field public static final String ACTION_MANAGE_ALL_SIM_PROFILES_SETTINGS = "android.settings.MANAGE_ALL_SIM_PROFILES_SETTINGS";
field public static final String ACTION_MANAGE_APPLICATIONS_SETTINGS = "android.settings.MANAGE_APPLICATIONS_SETTINGS";
field public static final String ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION = "android.settings.MANAGE_APP_ALL_FILES_ACCESS_PERMISSION";
- field public static final String ACTION_MANAGE_APP_LONG_JOBS = "android.settings.MANAGE_APP_LONG_JOBS";
+ field public static final String ACTION_MANAGE_APP_LONG_RUNNING_JOBS = "android.settings.MANAGE_APP_LONG_RUNNING_JOBS";
field public static final String ACTION_MANAGE_DEFAULT_APPS_SETTINGS = "android.settings.MANAGE_DEFAULT_APPS_SETTINGS";
field public static final String ACTION_MANAGE_OVERLAY_PERMISSION = "android.settings.action.MANAGE_OVERLAY_PERMISSION";
field public static final String ACTION_MANAGE_SUPERVISOR_RESTRICTED_SETTING = "android.settings.MANAGE_SUPERVISOR_RESTRICTED_SETTING";
@@ -38116,6 +38178,7 @@ package android.security {
field public static final int ERROR_PERMISSION_DENIED = 5; // 0x5
field public static final int ERROR_UNIMPLEMENTED = 12; // 0xc
field public static final int ERROR_USER_AUTHENTICATION_REQUIRED = 2; // 0x2
+ field public static final int RETRY_AFTER_NEXT_REBOOT = 4; // 0x4
field public static final int RETRY_NEVER = 1; // 0x1
field public static final int RETRY_WHEN_CONNECTIVITY_AVAILABLE = 3; // 0x3
field public static final int RETRY_WITH_EXPONENTIAL_BACKOFF = 2; // 0x2
@@ -48748,7 +48811,7 @@ package android.view {
method public float getDesiredMaxAverageLuminance();
method public float getDesiredMaxLuminance();
method public float getDesiredMinLuminance();
- method public int[] getSupportedHdrTypes();
+ method @Deprecated public int[] getSupportedHdrTypes();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.view.Display.HdrCapabilities> CREATOR;
field public static final int HDR_TYPE_DOLBY_VISION = 1; // 0x1
@@ -48765,6 +48828,7 @@ package android.view {
method public int getPhysicalHeight();
method public int getPhysicalWidth();
method public float getRefreshRate();
+ method @NonNull public int[] getSupportedHdrTypes();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.view.Display.Mode> CREATOR;
}
@@ -52701,7 +52765,7 @@ package android.view.accessibility {
method public android.view.accessibility.AccessibilityNodeInfo getLabeledBy();
method public int getLiveRegion();
method public int getMaxTextLength();
- method public int getMinMillisBetweenContentChanges();
+ method @NonNull public java.time.Duration getMinDurationBetweenContentChanges();
method public int getMovementGranularities();
method public CharSequence getPackageName();
method @Nullable public CharSequence getPaneTitle();
@@ -52790,7 +52854,7 @@ package android.view.accessibility {
method public void setLiveRegion(int);
method public void setLongClickable(boolean);
method public void setMaxTextLength(int);
- method public void setMinMillisBetweenContentChanges(int);
+ method public void setMinDurationBetweenContentChanges(@NonNull java.time.Duration);
method public void setMovementGranularities(int);
method public void setMultiLine(boolean);
method public void setPackageName(CharSequence);
@@ -59149,6 +59213,22 @@ package android.widget.inline {
package android.window {
+ public final class BackEvent {
+ ctor public BackEvent(float, float, float, int);
+ method @FloatRange(from=0, to=1) public float getProgress();
+ method public int getSwipeEdge();
+ method public float getTouchX();
+ method public float getTouchY();
+ field public static final int EDGE_LEFT = 0; // 0x0
+ field public static final int EDGE_RIGHT = 1; // 0x1
+ }
+
+ public interface OnBackAnimationCallback extends android.window.OnBackInvokedCallback {
+ method public default void onBackCancelled();
+ method public default void onBackProgressed(@NonNull android.window.BackEvent);
+ method public default void onBackStarted(@NonNull android.window.BackEvent);
+ }
+
public interface OnBackInvokedCallback {
method public void onBackInvoked();
}
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 286a80051809..98c78fefcc60 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -418,11 +418,24 @@ package android.provider {
method @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public static java.util.Map<java.lang.String,java.util.List<android.content.ContentValues>> queryRawContactEntity(@NonNull android.content.ContentResolver, long);
}
- public final class DeviceConfig {
- field public static final String NAMESPACE_ALARM_MANAGER = "alarm_manager";
- field public static final String NAMESPACE_APP_CLONING = "app_cloning";
- field public static final String NAMESPACE_APP_STANDBY = "app_standby";
- field public static final String NAMESPACE_DEVICE_IDLE = "device_idle";
+ public final class Settings {
+ field public static final int RESET_MODE_PACKAGE_DEFAULTS = 1; // 0x1
+ field public static final int RESET_MODE_TRUSTED_DEFAULTS = 4; // 0x4
+ field public static final int RESET_MODE_UNTRUSTED_CHANGES = 3; // 0x3
+ field public static final int RESET_MODE_UNTRUSTED_DEFAULTS = 2; // 0x2
+ }
+
+ public static final class Settings.Config extends android.provider.Settings.NameValueTable {
+ method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static boolean deleteString(@NonNull String, @NonNull String);
+ method @Nullable @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static String getString(@NonNull String);
+ method @NonNull @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static java.util.Map<java.lang.String,java.lang.String> getStrings(@NonNull String, @NonNull java.util.List<java.lang.String>);
+ method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static int getSyncDisabledMode();
+ method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static boolean putString(@NonNull String, @NonNull String, @Nullable String, boolean);
+ method public static void registerContentObserver(@Nullable String, boolean, @NonNull android.database.ContentObserver);
+ 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 setStrings(@NonNull String, @NonNull java.util.Map<java.lang.String,java.lang.String>) throws android.provider.DeviceConfig.BadConfigException;
+ method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static void setSyncDisabledMode(int);
+ method public static void unregisterContentObserver(@NonNull android.database.ContentObserver);
}
public static final class Settings.Global extends android.provider.Settings.NameValueTable {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 66423c8ede34..47faa3b34095 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -826,7 +826,6 @@ package android.app {
method public int describeContents();
method public int getFpsOverride();
method public float getScalingFactor();
- method @NonNull public android.app.GameModeConfiguration.Builder toBuilder();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.app.GameModeConfiguration> CREATOR;
field public static final int FPS_OVERRIDE_NONE = 0; // 0x0
@@ -834,6 +833,7 @@ package android.app {
public static final class GameModeConfiguration.Builder {
ctor public GameModeConfiguration.Builder();
+ ctor public GameModeConfiguration.Builder(@NonNull android.app.GameModeConfiguration);
method @NonNull public android.app.GameModeConfiguration build();
method @NonNull public android.app.GameModeConfiguration.Builder setFpsOverride(int);
method @NonNull public android.app.GameModeConfiguration.Builder setScalingFactor(float);
@@ -845,7 +845,7 @@ package android.app {
method public int getActiveGameMode();
method @NonNull public int[] getAvailableGameModes();
method @Nullable public android.app.GameModeConfiguration getGameModeConfiguration(int);
- method @NonNull public int[] getOptedInGameModes();
+ method @NonNull public int[] getOverriddenGameModes();
method public boolean isDownscalingAllowed();
method public boolean isFpsOverrideAllowed();
method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -860,7 +860,7 @@ package android.app {
method @NonNull public android.app.GameModeInfo.Builder setDownscalingAllowed(boolean);
method @NonNull public android.app.GameModeInfo.Builder setFpsOverrideAllowed(boolean);
method @NonNull public android.app.GameModeInfo.Builder setGameModeConfiguration(int, @NonNull android.app.GameModeConfiguration);
- method @NonNull public android.app.GameModeInfo.Builder setOptedInGameModes(@NonNull int[]);
+ method @NonNull public android.app.GameModeInfo.Builder setOverriddenGameModes(@NonNull int[]);
}
public abstract class InstantAppResolverService extends android.app.Service {
@@ -1474,6 +1474,7 @@ package android.app.backup {
method @RequiresPermission(android.Manifest.permission.BACKUP) public void cancelBackups();
method @RequiresPermission(android.Manifest.permission.BACKUP) public void excludeKeysFromRestore(@NonNull String, @NonNull java.util.List<java.lang.String>);
method @RequiresPermission(android.Manifest.permission.BACKUP) public long getAvailableRestoreToken(String);
+ method @NonNull public android.app.backup.BackupRestoreEventLogger getBackupRestoreEventLogger(@NonNull android.app.backup.BackupAgent);
method @RequiresPermission(android.Manifest.permission.BACKUP) public android.content.Intent getConfigurationIntent(String);
method @RequiresPermission(android.Manifest.permission.BACKUP) public String getCurrentTransport();
method @Nullable @RequiresPermission(android.Manifest.permission.BACKUP) public android.content.ComponentName getCurrentTransportComponent();
@@ -1592,6 +1593,33 @@ package android.app.backup {
field public final long bytesTransferred;
}
+ public class BackupRestoreEventLogger {
+ method public void logBackupMetaData(@android.app.backup.BackupRestoreEventLogger.BackupRestoreDataType @NonNull String, @NonNull String);
+ method public void logItemsBackedUp(@android.app.backup.BackupRestoreEventLogger.BackupRestoreDataType @NonNull String, int);
+ method public void logItemsBackupFailed(@android.app.backup.BackupRestoreEventLogger.BackupRestoreDataType @NonNull String, int, @android.app.backup.BackupRestoreEventLogger.BackupRestoreError @Nullable String);
+ method public void logItemsRestoreFailed(@android.app.backup.BackupRestoreEventLogger.BackupRestoreDataType @NonNull String, int, @android.app.backup.BackupRestoreEventLogger.BackupRestoreError @Nullable String);
+ method public void logItemsRestored(@android.app.backup.BackupRestoreEventLogger.BackupRestoreDataType @NonNull String, int);
+ method public void logRestoreMetadata(@android.app.backup.BackupRestoreEventLogger.BackupRestoreDataType @NonNull String, @NonNull String);
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface BackupRestoreEventLogger.BackupRestoreDataType {
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface BackupRestoreEventLogger.BackupRestoreError {
+ }
+
+ public static final class BackupRestoreEventLogger.DataTypeResult implements android.os.Parcelable {
+ ctor public BackupRestoreEventLogger.DataTypeResult(@NonNull String);
+ method public int describeContents();
+ method @android.app.backup.BackupRestoreEventLogger.BackupRestoreDataType @NonNull public String getDataType();
+ method @NonNull public java.util.Map<java.lang.String,java.lang.Integer> getErrors();
+ method public int getFailCount();
+ method @Nullable public byte[] getMetadataHash();
+ method public int getSuccessCount();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.backup.BackupRestoreEventLogger.DataTypeResult> CREATOR;
+ }
+
public class BackupTransport {
ctor public BackupTransport();
method public int abortFullRestore();
@@ -2956,13 +2984,17 @@ package android.companion.virtual {
public static class VirtualDeviceManager.VirtualDevice implements java.lang.AutoCloseable {
method public void addActivityListener(@NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.VirtualDeviceManager.ActivityListener);
method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void close();
+ method @NonNull public android.content.Context createContext();
method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.companion.virtual.audio.VirtualAudioDevice createVirtualAudioDevice(@NonNull android.hardware.display.VirtualDisplay, @Nullable java.util.concurrent.Executor, @Nullable android.companion.virtual.audio.VirtualAudioDevice.AudioConfigurationChangeCallback);
method @Nullable public android.hardware.display.VirtualDisplay createVirtualDisplay(@IntRange(from=1) int, @IntRange(from=1) int, @IntRange(from=1) int, @Nullable android.view.Surface, int, @Nullable java.util.concurrent.Executor, @Nullable android.hardware.display.VirtualDisplay.Callback);
method @Nullable public android.hardware.display.VirtualDisplay createVirtualDisplay(@IntRange(from=1) int, @IntRange(from=1) int, @IntRange(from=1) int, @NonNull java.util.List<java.lang.String>, @Nullable android.view.Surface, int, @Nullable java.util.concurrent.Executor, @Nullable android.hardware.display.VirtualDisplay.Callback);
- method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualDpad createVirtualDpad(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int);
- method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualKeyboard createVirtualKeyboard(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int);
- method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualMouse createVirtualMouse(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int);
- method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualTouchscreen createVirtualTouchscreen(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int);
+ method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualDpad createVirtualDpad(@NonNull android.hardware.input.VirtualDpadConfig);
+ method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualKeyboard createVirtualKeyboard(@NonNull android.hardware.input.VirtualKeyboardConfig);
+ method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualKeyboard createVirtualKeyboard(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int);
+ method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualMouse createVirtualMouse(@NonNull android.hardware.input.VirtualMouseConfig);
+ method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualMouse createVirtualMouse(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int);
+ method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualTouchscreen createVirtualTouchscreen(@NonNull android.hardware.input.VirtualTouchscreenConfig);
+ method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualTouchscreen createVirtualTouchscreen(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int);
method public int getDeviceId();
method @Nullable public android.companion.virtual.sensor.VirtualSensor getVirtualSensor(int, @NonNull String);
method public void launchPendingIntent(int, @NonNull android.app.PendingIntent, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer);
@@ -2978,6 +3010,7 @@ package android.companion.virtual {
method @NonNull public java.util.Set<android.content.ComponentName> getBlockedCrossTaskNavigations();
method public int getDefaultActivityPolicy();
method public int getDefaultNavigationPolicy();
+ method public int getDefaultRecentsPolicy();
method public int getDevicePolicy(int);
method public int getLockState();
method @Nullable public String getName();
@@ -2994,6 +3027,7 @@ package android.companion.virtual {
field public static final int NAVIGATION_POLICY_DEFAULT_ALLOWED = 0; // 0x0
field public static final int NAVIGATION_POLICY_DEFAULT_BLOCKED = 1; // 0x1
field public static final int POLICY_TYPE_SENSORS = 0; // 0x0
+ field public static final int RECENTS_POLICY_ALLOW_IN_HOST_DEVICE_RECENTS = 1; // 0x1
}
public static final class VirtualDeviceParams.Builder {
@@ -3004,6 +3038,7 @@ package android.companion.virtual {
method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setAllowedCrossTaskNavigations(@NonNull java.util.Set<android.content.ComponentName>);
method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setBlockedActivities(@NonNull java.util.Set<android.content.ComponentName>);
method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setBlockedCrossTaskNavigations(@NonNull java.util.Set<android.content.ComponentName>);
+ method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setDefaultRecentsPolicy(int);
method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setDevicePolicy(int, int);
method @NonNull @RequiresPermission(value=android.Manifest.permission.ADD_ALWAYS_UNLOCKED_DISPLAY, conditional=true) public android.companion.virtual.VirtualDeviceParams.Builder setLockState(int);
method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setName(@NonNull String);
@@ -3062,7 +3097,7 @@ package android.companion.virtual.sensor {
public class VirtualSensor {
method @NonNull public String getName();
method public int getType();
- method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void sendSensorEvent(@NonNull android.companion.virtual.sensor.VirtualSensorEvent);
+ method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void sendEvent(@NonNull android.companion.virtual.sensor.VirtualSensorEvent);
}
public static interface VirtualSensor.SensorStateChangeCallback {
@@ -3737,6 +3772,7 @@ package android.content.pm {
field @Deprecated public static final int PROTECTION_FLAG_DOCUMENTER = 262144; // 0x40000
field public static final int PROTECTION_FLAG_INCIDENT_REPORT_APPROVER = 1048576; // 0x100000
field public static final int PROTECTION_FLAG_KNOWN_SIGNER = 134217728; // 0x8000000
+ field public static final int PROTECTION_FLAG_MODULE = 4194304; // 0x400000
field public static final int PROTECTION_FLAG_OEM = 16384; // 0x4000
field public static final int PROTECTION_FLAG_RECENTS = 33554432; // 0x2000000
field public static final int PROTECTION_FLAG_RETAIL_DEMO = 16777216; // 0x1000000
@@ -4609,6 +4645,34 @@ package android.hardware.input {
method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void sendKeyEvent(@NonNull android.hardware.input.VirtualKeyEvent);
}
+ public final class VirtualDpadConfig extends android.hardware.input.VirtualInputDeviceConfig implements android.os.Parcelable {
+ method public int describeContents();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.hardware.input.VirtualDpadConfig> CREATOR;
+ }
+
+ public static final class VirtualDpadConfig.Builder extends android.hardware.input.VirtualInputDeviceConfig.Builder<android.hardware.input.VirtualDpadConfig.Builder> {
+ ctor public VirtualDpadConfig.Builder();
+ method @NonNull public android.hardware.input.VirtualDpadConfig build();
+ }
+
+ public abstract class VirtualInputDeviceConfig {
+ ctor protected VirtualInputDeviceConfig(@NonNull android.hardware.input.VirtualInputDeviceConfig.Builder<? extends android.hardware.input.VirtualInputDeviceConfig.Builder<?>>);
+ ctor protected VirtualInputDeviceConfig(@NonNull android.os.Parcel);
+ method public int getAssociatedDisplayId();
+ method @NonNull public String getInputDeviceName();
+ method public int getProductId();
+ method public int getVendorId();
+ }
+
+ public abstract static class VirtualInputDeviceConfig.Builder<T extends android.hardware.input.VirtualInputDeviceConfig.Builder<T>> {
+ ctor public VirtualInputDeviceConfig.Builder();
+ method @NonNull public T setAssociatedDisplayId(int);
+ method @NonNull public T setInputDeviceName(@NonNull String);
+ method @NonNull public T setProductId(int);
+ method @NonNull public T setVendorId(int);
+ }
+
public final class VirtualKeyEvent implements android.os.Parcelable {
method public int describeContents();
method public int getAction();
@@ -4631,6 +4695,17 @@ package android.hardware.input {
method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void sendKeyEvent(@NonNull android.hardware.input.VirtualKeyEvent);
}
+ public final class VirtualKeyboardConfig extends android.hardware.input.VirtualInputDeviceConfig implements android.os.Parcelable {
+ method public int describeContents();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.hardware.input.VirtualKeyboardConfig> CREATOR;
+ }
+
+ public static final class VirtualKeyboardConfig.Builder extends android.hardware.input.VirtualInputDeviceConfig.Builder<android.hardware.input.VirtualKeyboardConfig.Builder> {
+ ctor public VirtualKeyboardConfig.Builder();
+ method @NonNull public android.hardware.input.VirtualKeyboardConfig build();
+ }
+
public class VirtualMouse implements java.io.Closeable {
method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void close();
method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.graphics.PointF getCursorPosition();
@@ -4661,6 +4736,17 @@ package android.hardware.input {
method @NonNull public android.hardware.input.VirtualMouseButtonEvent.Builder setButtonCode(int);
}
+ public final class VirtualMouseConfig extends android.hardware.input.VirtualInputDeviceConfig implements android.os.Parcelable {
+ method public int describeContents();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.hardware.input.VirtualMouseConfig> CREATOR;
+ }
+
+ public static final class VirtualMouseConfig.Builder extends android.hardware.input.VirtualInputDeviceConfig.Builder<android.hardware.input.VirtualMouseConfig.Builder> {
+ ctor public VirtualMouseConfig.Builder();
+ method @NonNull public android.hardware.input.VirtualMouseConfig build();
+ }
+
public final class VirtualMouseRelativeEvent implements android.os.Parcelable {
method public int describeContents();
method public float getRelativeX();
@@ -4727,6 +4813,21 @@ package android.hardware.input {
method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void sendTouchEvent(@NonNull android.hardware.input.VirtualTouchEvent);
}
+ public final class VirtualTouchscreenConfig extends android.hardware.input.VirtualInputDeviceConfig implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getHeightInPixels();
+ method public int getWidthInPixels();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.hardware.input.VirtualTouchscreenConfig> CREATOR;
+ }
+
+ public static final class VirtualTouchscreenConfig.Builder extends android.hardware.input.VirtualInputDeviceConfig.Builder<android.hardware.input.VirtualTouchscreenConfig.Builder> {
+ ctor public VirtualTouchscreenConfig.Builder();
+ method @NonNull public android.hardware.input.VirtualTouchscreenConfig build();
+ method @NonNull public android.hardware.input.VirtualTouchscreenConfig.Builder setHeightInPixels(int);
+ method @NonNull public android.hardware.input.VirtualTouchscreenConfig.Builder setWidthInPixels(int);
+ }
+
}
package android.hardware.lights {
@@ -9322,6 +9423,7 @@ package android.net.wifi.nl80211 {
}
public class WifiNl80211Manager {
+ ctor public WifiNl80211Manager(@NonNull android.content.Context, @NonNull android.os.IBinder);
method public void abortScan(@NonNull String);
method public void enableVerboseLogging(boolean);
method @NonNull public int[] getChannelsMhzForBand(int);
@@ -10123,7 +10225,7 @@ package android.os {
method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED}) public android.graphics.Bitmap getUserIcon();
method @Deprecated @android.os.UserManager.UserRestrictionSource @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.QUERY_USERS}) public int getUserRestrictionSource(String, android.os.UserHandle);
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.QUERY_USERS}) public java.util.List<android.os.UserManager.EnforcingUser> getUserRestrictionSources(String, android.os.UserHandle);
- method @RequiresPermission(allOf={android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public int getUserSwitchability();
+ method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public int getUserSwitchability();
method @NonNull @RequiresPermission(anyOf={"android.permission.INTERACT_ACROSS_USERS", "android.permission.MANAGE_USERS"}) public java.util.Set<android.os.UserHandle> getVisibleUsers();
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean hasRestrictedProfiles();
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean hasUserRestrictionForUser(@NonNull String, @NonNull android.os.UserHandle);
@@ -10605,112 +10707,6 @@ package android.provider {
method @RequiresPermission("android.contacts.permission.MANAGE_SIM_ACCOUNTS") public static void removeSimAccounts(@NonNull android.content.ContentResolver, int);
}
- public final class DeviceConfig {
- method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static void addOnPropertiesChangedListener(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.provider.DeviceConfig.OnPropertiesChangedListener);
- method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static boolean deleteProperty(@NonNull String, @NonNull String);
- method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static boolean getBoolean(@NonNull String, @NonNull String, boolean);
- method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static float getFloat(@NonNull String, @NonNull String, float);
- method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static int getInt(@NonNull String, @NonNull String, int);
- method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static long getLong(@NonNull String, @NonNull String, long);
- method @NonNull @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static android.provider.DeviceConfig.Properties getProperties(@NonNull String, @NonNull java.lang.String...);
- method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static String getProperty(@NonNull String, @NonNull String);
- method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static String getString(@NonNull String, @NonNull String, @Nullable String);
- method public static void removeOnPropertiesChangedListener(@NonNull android.provider.DeviceConfig.OnPropertiesChangedListener);
- method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static void resetToDefaults(int, @Nullable String);
- method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static boolean setProperties(@NonNull android.provider.DeviceConfig.Properties) throws android.provider.DeviceConfig.BadConfigException;
- method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static boolean setProperty(@NonNull String, @NonNull String, @Nullable String, boolean);
- field public static final String NAMESPACE_ACTIVITY_MANAGER = "activity_manager";
- field public static final String NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT = "activity_manager_native_boot";
- field public static final String NAMESPACE_ADSERVICES = "adservices";
- field public static final String NAMESPACE_AMBIENT_CONTEXT_MANAGER_SERVICE = "ambient_context_manager_service";
- field public static final String NAMESPACE_APPSEARCH = "appsearch";
- field public static final String NAMESPACE_APP_COMPAT = "app_compat";
- field public static final String NAMESPACE_APP_HIBERNATION = "app_hibernation";
- field public static final String NAMESPACE_ATTENTION_MANAGER_SERVICE = "attention_manager_service";
- field public static final String NAMESPACE_AUTOFILL = "autofill";
- field public static final String NAMESPACE_BACKUP_AND_RESTORE = "backup_and_restore";
- field public static final String NAMESPACE_BATTERY_SAVER = "battery_saver";
- field public static final String NAMESPACE_BIOMETRICS = "biometrics";
- field public static final String NAMESPACE_BLOBSTORE = "blobstore";
- field public static final String NAMESPACE_BLUETOOTH = "bluetooth";
- field public static final String NAMESPACE_CAPTIVEPORTALLOGIN = "captive_portal_login";
- field public static final String NAMESPACE_CLIPBOARD = "clipboard";
- field public static final String NAMESPACE_CONNECTIVITY = "connectivity";
- field public static final String NAMESPACE_CONTENT_CAPTURE = "content_capture";
- field @Deprecated public static final String NAMESPACE_DEX_BOOT = "dex_boot";
- field public static final String NAMESPACE_DISPLAY_MANAGER = "display_manager";
- field public static final String NAMESPACE_GAME_DRIVER = "game_driver";
- field public static final String NAMESPACE_HDMI_CONTROL = "hdmi_control";
- field public static final String NAMESPACE_INPUT_NATIVE_BOOT = "input_native_boot";
- field public static final String NAMESPACE_INTELLIGENCE_ATTENTION = "intelligence_attention";
- field public static final String NAMESPACE_LMKD_NATIVE = "lmkd_native";
- field public static final String NAMESPACE_LOCATION = "location";
- field public static final String NAMESPACE_MEDIA = "media";
- field public static final String NAMESPACE_MEDIA_NATIVE = "media_native";
- field public static final String NAMESPACE_NEARBY = "nearby";
- field public static final String NAMESPACE_NETD_NATIVE = "netd_native";
- field public static final String NAMESPACE_NNAPI_NATIVE = "nnapi_native";
- field public static final String NAMESPACE_ON_DEVICE_PERSONALIZATION = "on_device_personalization";
- field public static final String NAMESPACE_OTA = "ota";
- field public static final String NAMESPACE_PACKAGE_MANAGER_SERVICE = "package_manager_service";
- field public static final String NAMESPACE_PERMISSIONS = "permissions";
- field public static final String NAMESPACE_PRIVACY = "privacy";
- field public static final String NAMESPACE_PROFCOLLECT_NATIVE_BOOT = "profcollect_native_boot";
- field public static final String NAMESPACE_REBOOT_READINESS = "reboot_readiness";
- field public static final String NAMESPACE_ROLLBACK = "rollback";
- field public static final String NAMESPACE_ROLLBACK_BOOT = "rollback_boot";
- field public static final String NAMESPACE_RUNTIME = "runtime";
- field public static final String NAMESPACE_RUNTIME_NATIVE = "runtime_native";
- field public static final String NAMESPACE_RUNTIME_NATIVE_BOOT = "runtime_native_boot";
- field public static final String NAMESPACE_SCHEDULER = "scheduler";
- field public static final String NAMESPACE_SDK_SANDBOX = "sdk_sandbox";
- field public static final String NAMESPACE_STATSD_JAVA = "statsd_java";
- field public static final String NAMESPACE_STATSD_JAVA_BOOT = "statsd_java_boot";
- field public static final String NAMESPACE_STATSD_NATIVE = "statsd_native";
- field public static final String NAMESPACE_STATSD_NATIVE_BOOT = "statsd_native_boot";
- field @Deprecated public static final String NAMESPACE_STORAGE = "storage";
- field public static final String NAMESPACE_STORAGE_NATIVE_BOOT = "storage_native_boot";
- field public static final String NAMESPACE_SURFACE_FLINGER_NATIVE_BOOT = "surface_flinger_native_boot";
- field public static final String NAMESPACE_SWCODEC_NATIVE = "swcodec_native";
- field public static final String NAMESPACE_SYSTEMUI = "systemui";
- field public static final String NAMESPACE_SYSTEM_TIME = "system_time";
- field public static final String NAMESPACE_TELEPHONY = "telephony";
- field public static final String NAMESPACE_TETHERING = "tethering";
- field public static final String NAMESPACE_TEXTCLASSIFIER = "textclassifier";
- field public static final String NAMESPACE_TRANSPARENCY_METADATA = "transparency_metadata";
- field public static final String NAMESPACE_UWB = "uwb";
- field public static final String NAMESPACE_WEARABLE_SENSING = "wearable_sensing";
- field public static final String NAMESPACE_WINDOW_MANAGER_NATIVE_BOOT = "window_manager_native_boot";
- }
-
- public static class DeviceConfig.BadConfigException extends java.lang.Exception {
- ctor public DeviceConfig.BadConfigException();
- }
-
- public static interface DeviceConfig.OnPropertiesChangedListener {
- method public void onPropertiesChanged(@NonNull android.provider.DeviceConfig.Properties);
- }
-
- public static class DeviceConfig.Properties {
- method public boolean getBoolean(@NonNull String, boolean);
- method public float getFloat(@NonNull String, float);
- method public int getInt(@NonNull String, int);
- method @NonNull public java.util.Set<java.lang.String> getKeyset();
- method public long getLong(@NonNull String, long);
- method @NonNull public String getNamespace();
- method @Nullable public String getString(@NonNull String, @Nullable String);
- }
-
- public static final class DeviceConfig.Properties.Builder {
- ctor public DeviceConfig.Properties.Builder(@NonNull String);
- method @NonNull public android.provider.DeviceConfig.Properties build();
- method @NonNull public android.provider.DeviceConfig.Properties.Builder setBoolean(@NonNull String, boolean);
- method @NonNull public android.provider.DeviceConfig.Properties.Builder setFloat(@NonNull String, float);
- method @NonNull public android.provider.DeviceConfig.Properties.Builder setInt(@NonNull String, int);
- method @NonNull public android.provider.DeviceConfig.Properties.Builder setLong(@NonNull String, long);
- method @NonNull public android.provider.DeviceConfig.Properties.Builder setString(@NonNull String, @Nullable String);
- }
-
public final class DocumentsContract {
method @NonNull public static android.net.Uri buildDocumentUriAsUser(@NonNull String, @NonNull String, @NonNull android.os.UserHandle);
method public static boolean isManageMode(@NonNull android.net.Uri);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 7d85fafe9e9c..3d30c0f6d0b0 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -38,6 +38,7 @@ package android {
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 RECORD_BACKGROUND_AUDIO = "android.permission.RECORD_BACKGROUND_AUDIO";
+ field public static final String REMAP_MODIFIER_KEYS = "android.permission.REMAP_MODIFIER_KEYS";
field public static final String REMOVE_TASKS = "android.permission.REMOVE_TASKS";
field public static final String REQUEST_UNIQUE_ID_ATTESTATION = "android.permission.REQUEST_UNIQUE_ID_ATTESTATION";
field public static final String RESET_APP_ERRORS = "android.permission.RESET_APP_ERRORS";
@@ -440,6 +441,7 @@ package android.app {
method @NonNull public java.util.Set<java.lang.String> getAdoptedShellPermissions();
method @Deprecated public boolean grantRuntimePermission(String, String, android.os.UserHandle);
method public boolean injectInputEvent(@NonNull android.view.InputEvent, boolean, boolean);
+ method public void injectInputEventToInputFilter(@NonNull android.view.InputEvent);
method @Deprecated public boolean revokeRuntimePermission(String, String, android.os.UserHandle);
method public void syncInputTransactions();
method public void syncInputTransactions(boolean);
@@ -1293,8 +1295,11 @@ package android.hardware.input {
public final class InputManager {
method public void addUniqueIdAssociation(@NonNull String, @NonNull String);
+ method @RequiresPermission(android.Manifest.permission.REMAP_MODIFIER_KEYS) public void clearAllModifierKeyRemappings();
method @Nullable public String getCurrentKeyboardLayoutForInputDevice(@NonNull android.hardware.input.InputDeviceIdentifier);
method @NonNull public java.util.List<java.lang.String> getKeyboardLayoutDescriptorsForInputDevice(@NonNull android.view.InputDevice);
+ method @NonNull @RequiresPermission(android.Manifest.permission.REMAP_MODIFIER_KEYS) public java.util.Map<java.lang.Integer,java.lang.Integer> getModifierKeyRemapping();
+ method @RequiresPermission(android.Manifest.permission.REMAP_MODIFIER_KEYS) public void remapModifierKey(int, int);
method @RequiresPermission(android.Manifest.permission.SET_KEYBOARD_LAYOUT) public void removeKeyboardLayoutForInputDevice(@NonNull android.hardware.input.InputDeviceIdentifier, @NonNull String);
method public void removeUniqueIdAssociation(@NonNull String);
method @RequiresPermission(android.Manifest.permission.SET_KEYBOARD_LAYOUT) public void setCurrentKeyboardLayoutForInputDevice(@NonNull android.hardware.input.InputDeviceIdentifier, @NonNull String);
@@ -1316,6 +1321,14 @@ package android.hardware.lights {
}
+package android.hardware.location {
+
+ public final class ContextHubManager {
+ method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public long[] getPreloadedNanoAppIds(@NonNull android.hardware.location.ContextHubInfo);
+ }
+
+}
+
package android.hardware.soundtrigger {
public class KeyphraseEnrollmentInfo {
@@ -1502,13 +1515,33 @@ package android.media {
method public static boolean isEncodingLinearPcm(int);
}
+ public final class AudioHalVersionInfo implements java.lang.Comparable<android.media.AudioHalVersionInfo> android.os.Parcelable {
+ method public int compareTo(@NonNull android.media.AudioHalVersionInfo);
+ method public int describeContents();
+ method public int getHalType();
+ method public int getMajorVersion();
+ method public int getMinorVersion();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.media.AudioHalVersionInfo AIDL_1_0;
+ field public static final int AUDIO_HAL_TYPE_AIDL = 1; // 0x1
+ field public static final int AUDIO_HAL_TYPE_HIDL = 0; // 0x0
+ field @NonNull public static final android.os.Parcelable.Creator<android.media.AudioHalVersionInfo> CREATOR;
+ field @NonNull public static final android.media.AudioHalVersionInfo HIDL_2_0;
+ field @NonNull public static final android.media.AudioHalVersionInfo HIDL_4_0;
+ field @NonNull public static final android.media.AudioHalVersionInfo HIDL_5_0;
+ field @NonNull public static final android.media.AudioHalVersionInfo HIDL_6_0;
+ field @NonNull public static final android.media.AudioHalVersionInfo HIDL_7_0;
+ field @NonNull public static final android.media.AudioHalVersionInfo HIDL_7_1;
+ field @NonNull public static final java.util.List<android.media.AudioHalVersionInfo> VERSIONS;
+ }
+
public class AudioManager {
method @RequiresPermission("android.permission.QUERY_AUDIO_STATE") public int abandonAudioFocusForTest(@NonNull android.media.AudioFocusRequest, @NonNull String);
method @NonNull @RequiresPermission(android.Manifest.permission.CALL_AUDIO_INTERCEPTION) public android.media.AudioRecord getCallDownlinkExtractionAudioRecord(@NonNull android.media.AudioFormat);
method @NonNull @RequiresPermission(android.Manifest.permission.CALL_AUDIO_INTERCEPTION) public android.media.AudioTrack getCallUplinkInjectionAudioTrack(@NonNull android.media.AudioFormat);
method @Nullable public static android.media.AudioDeviceInfo getDeviceInfoFromType(int);
method @IntRange(from=0) @RequiresPermission("android.permission.QUERY_AUDIO_STATE") public long getFadeOutDurationOnFocusLossMillis(@NonNull android.media.AudioAttributes);
- method @Nullable public static String getHalVersion();
+ method @Nullable public static android.media.AudioHalVersionInfo getHalVersion();
method public static final int[] getPublicStreamTypes();
method @NonNull public java.util.List<java.lang.Integer> getReportedSurroundFormats();
method public int getStreamMinVolumeInt(int);
@@ -1710,7 +1743,6 @@ package android.os {
public class Build {
method public static boolean is64BitAbi(String);
method public static boolean isDebuggable();
- method public static boolean isSecure();
field public static final boolean IS_EMULATOR;
}
@@ -2182,17 +2214,6 @@ package android.provider {
field public static final android.net.Uri CORP_CONTENT_URI;
}
- public final class DeviceConfig {
- field public static final String NAMESPACE_ALARM_MANAGER = "alarm_manager";
- field public static final String NAMESPACE_ANDROID = "android";
- field public static final String NAMESPACE_APP_COMPAT_OVERRIDES = "app_compat_overrides";
- field public static final String NAMESPACE_CONSTRAIN_DISPLAY_APIS = "constrain_display_apis";
- field public static final String NAMESPACE_DEVICE_IDLE = "device_idle";
- field public static final String NAMESPACE_INPUT_METHOD_MANAGER = "input_method_manager";
- field public static final String NAMESPACE_JOB_SCHEDULER = "jobscheduler";
- field public static final String NAMESPACE_SELECTION_TOOLBAR = "selection_toolbar";
- }
-
public interface InputMethodManagerDeviceConfig {
field public static final String KEY_HIDE_IME_WHEN_NO_EDITOR_FOCUS = "hide_ime_when_no_editor_focus";
}
diff --git a/core/java/Android.bp b/core/java/Android.bp
index a4a12d743442..738e2de7d44b 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -368,6 +368,20 @@ aidl_interface {
},
}
+// Build Rust bindings for remote provisioning. Needed by keystore2.
+aidl_interface {
+ name: "android.security.rkp_aidl",
+ unstable: true,
+ srcs: [
+ "android/security/rkp/*.aidl",
+ ],
+ backend: {
+ rust: {
+ enabled: true,
+ },
+ },
+}
+
aidl_interface {
name: "android.debug_aidl",
unstable: true,
@@ -424,6 +438,7 @@ filegroup {
"android/os/IInterface.java",
"android/os/Binder.java",
"android/os/IBinder.java",
+ "android/os/Parcelable.java",
],
}
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index 02a81acf791a..968ed87dde4f 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -54,6 +54,7 @@ import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
import android.view.Display;
+import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.SurfaceControl;
@@ -797,6 +798,8 @@ public abstract class AccessibilityService extends Service {
private FingerprintGestureController mFingerprintGestureController;
+ private int mMotionEventSources;
+
/**
* Callback for {@link android.view.accessibility.AccessibilityEvent}s.
*
@@ -820,7 +823,11 @@ public abstract class AccessibilityService extends Service {
for (int i = 0; i < mMagnificationControllers.size(); i++) {
mMagnificationControllers.valueAt(i).onServiceConnectedLocked();
}
- updateInputMethod(getServiceInfo());
+ final AccessibilityServiceInfo info = getServiceInfo();
+ if (info != null) {
+ updateInputMethod(info);
+ mMotionEventSources = info.getMotionEventSources();
+ }
}
if (mSoftKeyboardController != null) {
mSoftKeyboardController.onServiceConnected();
@@ -946,6 +953,25 @@ public abstract class AccessibilityService extends Service {
}
/**
+ * Callback that allows an accessibility service to observe generic {@link MotionEvent}s.
+ * <p>
+ * Prefer {@link TouchInteractionController} to observe and control touchscreen events,
+ * including touch gestures. If this or any enabled service is using
+ * {@link AccessibilityServiceInfo#FLAG_REQUEST_TOUCH_EXPLORATION_MODE} then
+ * {@link #onMotionEvent} will not receive touchscreen events.
+ * </p>
+ * <p>
+ * <strong>Note:</strong> The service must first request to listen to events using
+ * {@link AccessibilityServiceInfo#setMotionEventSources}.
+ * {@link MotionEvent}s from sources in {@link AccessibilityServiceInfo#getMotionEventSources()}
+ * are not sent to the rest of the system. To stop listening to events from a given source, call
+ * {@link AccessibilityServiceInfo#setMotionEventSources} with that source removed.
+ * </p>
+ * @param event The event to be processed.
+ */
+ public void onMotionEvent(@NonNull MotionEvent event) { }
+
+ /**
* Gets the windows on the screen of the default display. This method returns only the windows
* that a sighted user can interact with, as opposed to all windows.
* For example, if there is a modal dialog shown and the user cannot touch
@@ -2521,6 +2547,7 @@ public abstract class AccessibilityService extends Service {
public final void setServiceInfo(AccessibilityServiceInfo info) {
mInfo = info;
updateInputMethod(info);
+ mMotionEventSources = info.getMotionEventSources();
sendServiceInfo();
}
@@ -2724,7 +2751,7 @@ public abstract class AccessibilityService extends Service {
@Override
public void onMotionEvent(MotionEvent event) {
- AccessibilityService.this.onMotionEvent(event);
+ AccessibilityService.this.sendMotionEventToCallback(event);
}
@Override
@@ -3359,14 +3386,23 @@ public abstract class AccessibilityService extends Service {
}
}
- void onMotionEvent(MotionEvent event) {
- TouchInteractionController controller;
- synchronized (mLock) {
- int displayId = event.getDisplayId();
- controller = mTouchInteractionControllers.get(displayId);
+ void sendMotionEventToCallback(MotionEvent event) {
+ boolean sendingTouchEventToTouchInteractionController = false;
+ if (event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN)) {
+ TouchInteractionController controller;
+ synchronized (mLock) {
+ int displayId = event.getDisplayId();
+ controller = mTouchInteractionControllers.get(displayId);
+ }
+ if (controller != null) {
+ sendingTouchEventToTouchInteractionController = true;
+ controller.onMotionEvent(event);
+ }
}
- if (controller != null) {
- controller.onMotionEvent(event);
+ final int eventSourceWithoutClass = event.getSource() & ~InputDevice.SOURCE_CLASS_MASK;
+ if ((mMotionEventSources & eventSourceWithoutClass) != 0
+ && !sendingTouchEventToTouchInteractionController) {
+ onMotionEvent(event);
}
}
diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
index 295eaafedfd3..ae57959de7ae 100644
--- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
@@ -612,6 +612,12 @@ public class AccessibilityServiceInfo implements Parcelable {
private boolean mIsAccessibilityTool = false;
/**
+ * The bit mask of {@link android.view.InputDevice} sources that the accessibility
+ * service wants to listen to for generic {@link android.view.MotionEvent}s.
+ */
+ private int mMotionEventSources = 0;
+
+ /**
* Creates a new instance.
*/
public AccessibilityServiceInfo() {
@@ -785,6 +791,7 @@ public class AccessibilityServiceInfo implements Parcelable {
mNonInteractiveUiTimeout = other.mNonInteractiveUiTimeout;
mInteractiveUiTimeout = other.mInteractiveUiTimeout;
flags = other.flags;
+ mMotionEventSources = other.mMotionEventSources;
// NOTE: Ensure that only properties that are safe to be modified by the service itself
// are included here (regardless of hidden setters, etc.).
}
@@ -956,6 +963,44 @@ public class AccessibilityServiceInfo implements Parcelable {
}
/**
+ * Returns the bit mask of {@link android.view.InputDevice} sources that the accessibility
+ * service wants to listen to for generic {@link android.view.MotionEvent}s.
+ */
+ public int getMotionEventSources() {
+ return mMotionEventSources;
+ }
+
+ /**
+ * Sets the bit mask of {@link android.view.InputDevice} sources that the accessibility
+ * service wants to listen to for generic {@link android.view.MotionEvent}s.
+ *
+ * <p>
+ * Note: including an {@link android.view.InputDevice} source that does not send
+ * {@link android.view.MotionEvent}s is effectively a no-op for that source, since you will
+ * not receive any events from that source.
+ * </p>
+ * <p>
+ * Allowed sources include:
+ * <li>{@link android.view.InputDevice#SOURCE_MOUSE}</li>
+ * <li>{@link android.view.InputDevice#SOURCE_STYLUS}</li>
+ * <li>{@link android.view.InputDevice#SOURCE_BLUETOOTH_STYLUS}</li>
+ * <li>{@link android.view.InputDevice#SOURCE_TRACKBALL}</li>
+ * <li>{@link android.view.InputDevice#SOURCE_MOUSE_RELATIVE}</li>
+ * <li>{@link android.view.InputDevice#SOURCE_TOUCHPAD}</li>
+ * <li>{@link android.view.InputDevice#SOURCE_TOUCH_NAVIGATION}</li>
+ * <li>{@link android.view.InputDevice#SOURCE_ROTARY_ENCODER}</li>
+ * <li>{@link android.view.InputDevice#SOURCE_JOYSTICK}</li>
+ * <li>{@link android.view.InputDevice#SOURCE_SENSOR}</li>
+ * </p>
+ *
+ * @param motionEventSources A bit mask of {@link android.view.InputDevice} sources.
+ * @see AccessibilityService#onMotionEvent
+ */
+ public void setMotionEventSources(int motionEventSources) {
+ mMotionEventSources = motionEventSources;
+ }
+
+ /**
* The localized summary of the accessibility service.
* <p>
* <strong>Statically set from
@@ -1179,6 +1224,7 @@ public class AccessibilityServiceInfo implements Parcelable {
parcel.writeBoolean(mIsAccessibilityTool);
parcel.writeString(mTileServiceName);
parcel.writeInt(mIntroResId);
+ parcel.writeInt(mMotionEventSources);
}
private void initFromParcel(Parcel parcel) {
@@ -1203,6 +1249,7 @@ public class AccessibilityServiceInfo implements Parcelable {
mIsAccessibilityTool = parcel.readBoolean();
mTileServiceName = parcel.readString();
mIntroResId = parcel.readInt();
+ mMotionEventSources = parcel.readInt();
}
@Override
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index f62190a7e6ca..c51e8aee61f0 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -902,4 +902,9 @@ public abstract class ActivityManagerInternal {
*/
public abstract void registerNetworkPolicyUidObserver(@NonNull IUidObserver observer,
int which, int cutpoint, @NonNull String callingPackage);
+
+ /**
+ * Return all client package names of a service.
+ */
+ public abstract ArraySet<String> getClientPackages(String servicePackageName);
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 897cd1f27e11..a4c9f8c88eda 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -1020,6 +1020,12 @@ public final class ActivityThread extends ClientTransactionHandler
int flags;
}
+ // A list of receivers and an index into the receiver to be processed next.
+ static final class ReceiverList {
+ List<ReceiverInfo> receivers;
+ int index;
+ }
+
private class ApplicationThread extends IApplicationThread.Stub {
private static final String DB_CONNECTION_INFO_HEADER = " %8s %8s %14s %5s %5s %5s %s";
private static final String DB_CONNECTION_INFO_FORMAT = " %8s %8s %14s %5d %5d %5d %s";
@@ -1036,6 +1042,21 @@ public final class ActivityThread extends ClientTransactionHandler
sendMessage(H.RECEIVER, r);
}
+ public final void scheduleReceiverList(List<ReceiverInfo> info) throws RemoteException {
+ for (int i = 0; i < info.size(); i++) {
+ ReceiverInfo r = info.get(i);
+ if (r.registered) {
+ scheduleRegisteredReceiver(r.receiver, r.intent,
+ r.resultCode, r.data, r.extras, r.ordered, r.sticky,
+ r.sendingUser, r.processState);
+ } else {
+ scheduleReceiver(r.intent, r.activityInfo, r.compatInfo,
+ r.resultCode, r.data, r.extras, r.sync,
+ r.sendingUser, r.processState);
+ }
+ }
+ }
+
public final void scheduleCreateBackupAgent(ApplicationInfo app,
int backupMode, int userId, @BackupDestination int backupDestination) {
CreateBackupAgentData d = new CreateBackupAgentData();
@@ -4763,14 +4784,16 @@ public final class ActivityThread extends ClientTransactionHandler
if (s != null) {
try {
if (localLOGV) Slog.v(TAG, "Timeout short service " + s);
- s.callOnTimeout(startId);
- // TODO(short-service): Do we need "service executing" for timeout?
- // (see handleStopService())
+ // Unlike other service callbacks, we don't do serviceDoneExecuting() here.
+ // "service executing" state is used to boost the procstate / oom-adj, but
+ // for short-FGS timeout, we have a specific control for them anyway, so
+ // we don't have to do that.
+ s.callOnTimeout(startId);
} catch (Exception e) {
if (!mInstrumentation.onException(s, e)) {
throw new RuntimeException(
- "Unable to timeout service " + s
+ "Unable to call onTimeout on service " + s
+ ": " + e.toString(), e);
}
Slog.i(TAG, "handleTimeoutService: exception for " + token, e);
@@ -6715,6 +6738,13 @@ public final class ActivityThread extends ClientTransactionHandler
ii = null;
}
+ final IActivityManager mgr = ActivityManager.getService();
+ try {
+ mgr.finishAttachApplication(mStartSeq);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+
final ContextImpl appContext = ContextImpl.createAppContext(this, data.info);
mConfigurationController.updateLocaleListFromAppContext(appContext);
@@ -6783,13 +6813,6 @@ public final class ActivityThread extends ClientTransactionHandler
final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskWrites();
final StrictMode.ThreadPolicy writesAllowedPolicy = StrictMode.getThreadPolicy();
- final IActivityManager mgr = ActivityManager.getService();
- try {
- mgr.finishAttachApplication(mStartSeq);
- } catch (RemoteException ex) {
- throw ex.rethrowFromSystemServer();
- }
-
// Wait for debugger after we have notified the system to finish attach application
if (data.debugMode != ApplicationThreadConstants.DEBUG_OFF) {
// XXX should have option to change the port.
diff --git a/core/java/android/app/ForegroundServiceTypePolicy.java b/core/java/android/app/ForegroundServiceTypePolicy.java
index 332aaddef950..e99e360a4583 100644
--- a/core/java/android/app/ForegroundServiceTypePolicy.java
+++ b/core/java/android/app/ForegroundServiceTypePolicy.java
@@ -23,6 +23,7 @@ import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA;
import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE;
import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_FILE_MANAGEMENT;
import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_HEALTH;
import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION;
import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MANIFEST;
@@ -412,6 +413,22 @@ public abstract class ForegroundServiceTypePolicy {
);
/**
+ * The policy for the {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_FILE_MANAGEMENT}.
+ *
+ * @hide
+ */
+ public static final @NonNull ForegroundServiceTypePolicyInfo FGS_TYPE_POLICY_FILE_MANAGEMENT =
+ new ForegroundServiceTypePolicyInfo(
+ FOREGROUND_SERVICE_TYPE_FILE_MANAGEMENT,
+ ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+ ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+ new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+ new RegularPermission(Manifest.permission.FOREGROUND_SERVICE_FILE_MANAGEMENT)
+ }, true),
+ null
+ );
+
+ /**
* The policy for the {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_SPECIAL_USE}.
*
* @hide
@@ -1057,6 +1074,8 @@ public abstract class ForegroundServiceTypePolicy {
FGS_TYPE_POLICY_SYSTEM_EXEMPTED);
mForegroundServiceTypePolicies.put(FOREGROUND_SERVICE_TYPE_SHORT_SERVICE,
FGS_TYPE_POLICY_SHORT_SERVICE);
+ mForegroundServiceTypePolicies.put(FOREGROUND_SERVICE_TYPE_FILE_MANAGEMENT,
+ FGS_TYPE_POLICY_FILE_MANAGEMENT);
mForegroundServiceTypePolicies.put(FOREGROUND_SERVICE_TYPE_SPECIAL_USE,
FGS_TYPE_POLICY_SPECIAL_USE);
}
diff --git a/core/java/android/app/GameManager.java b/core/java/android/app/GameManager.java
index 2f51b174113c..c6fa064a6b0f 100644
--- a/core/java/android/app/GameManager.java
+++ b/core/java/android/app/GameManager.java
@@ -287,6 +287,7 @@ public final class GameManager {
* <p>
* The caller must have {@link android.Manifest.permission#MANAGE_GAME_MODE}.
*
+ * @param packageName The package name of the game to update
* @param gameModeConfig The configuration to use for game mode interventions
* @hide
*/
diff --git a/core/java/android/app/GameModeConfiguration.java b/core/java/android/app/GameModeConfiguration.java
index b081e82e19b0..d8be814374ff 100644
--- a/core/java/android/app/GameModeConfiguration.java
+++ b/core/java/android/app/GameModeConfiguration.java
@@ -62,10 +62,16 @@ public final class GameModeConfiguration implements Parcelable {
*/
@SystemApi
public static final class Builder {
- /** Constructs a new Builder for a game mode’s configuration */
+ /** Constructs a new Builder for a game mode’s configuration. */
public Builder() {
}
+ /** Constructs a new builder by copying from an existing game mode configuration. */
+ public Builder(@NonNull GameModeConfiguration configuration) {
+ mFpsOverride = configuration.mFpsOverride;
+ mScalingFactor = configuration.mScalingFactor;
+ }
+
/**
* Sets the scaling factor used for game resolution downscaling.
* <br>
@@ -156,16 +162,6 @@ public final class GameModeConfiguration implements Parcelable {
return mFpsOverride;
}
- /**
- * Converts and returns the game mode config as a new builder.
- */
- @NonNull
- public GameModeConfiguration.Builder toBuilder() {
- return new GameModeConfiguration.Builder()
- .setFpsOverride(mFpsOverride)
- .setScalingFactor(mScalingFactor);
- }
-
@Override
public boolean equals(Object obj) {
if (obj == this) {
diff --git a/core/java/android/app/GameModeInfo.java b/core/java/android/app/GameModeInfo.java
index 31255c2e381f..7dcb3909f0b9 100644
--- a/core/java/android/app/GameModeInfo.java
+++ b/core/java/android/app/GameModeInfo.java
@@ -87,12 +87,12 @@ public final class GameModeInfo implements Parcelable {
}
/**
- * Sets the opted-in game modes.
+ * Sets the overridden game modes.
*/
@NonNull
- public GameModeInfo.Builder setOptedInGameModes(
- @NonNull @GameManager.GameMode int[] optedInGameModes) {
- mOptedInGameModes = optedInGameModes;
+ public GameModeInfo.Builder setOverriddenGameModes(
+ @NonNull @GameManager.GameMode int[] overriddenGameModes) {
+ mOverriddenGameModes = overriddenGameModes;
return this;
}
@@ -140,12 +140,12 @@ public final class GameModeInfo implements Parcelable {
*/
@NonNull
public GameModeInfo build() {
- return new GameModeInfo(mActiveGameMode, mAvailableGameModes, mOptedInGameModes,
+ return new GameModeInfo(mActiveGameMode, mAvailableGameModes, mOverriddenGameModes,
mIsDownscalingAllowed, mIsFpsOverrideAllowed, mConfigMap);
}
private @GameManager.GameMode int[] mAvailableGameModes = new int[]{};
- private @GameManager.GameMode int[] mOptedInGameModes = new int[]{};
+ private @GameManager.GameMode int[] mOverriddenGameModes = new int[]{};
private @GameManager.GameMode int mActiveGameMode;
private boolean mIsDownscalingAllowed;
private boolean mIsFpsOverrideAllowed;
@@ -164,11 +164,11 @@ public final class GameModeInfo implements Parcelable {
private GameModeInfo(@GameManager.GameMode int activeGameMode,
@NonNull @GameManager.GameMode int[] availableGameModes,
- @NonNull @GameManager.GameMode int[] optedInGameModes, boolean isDownscalingAllowed,
+ @NonNull @GameManager.GameMode int[] overriddenGameModes, boolean isDownscalingAllowed,
boolean isFpsOverrideAllowed, @NonNull Map<Integer, GameModeConfiguration> configMap) {
mActiveGameMode = activeGameMode;
mAvailableGameModes = Arrays.copyOf(availableGameModes, availableGameModes.length);
- mOptedInGameModes = Arrays.copyOf(optedInGameModes, optedInGameModes.length);
+ mOverriddenGameModes = Arrays.copyOf(overriddenGameModes, overriddenGameModes.length);
mIsDownscalingAllowed = isDownscalingAllowed;
mIsFpsOverrideAllowed = isFpsOverrideAllowed;
mConfigMap = configMap;
@@ -179,7 +179,7 @@ public final class GameModeInfo implements Parcelable {
public GameModeInfo(Parcel in) {
mActiveGameMode = in.readInt();
mAvailableGameModes = in.createIntArray();
- mOptedInGameModes = in.createIntArray();
+ mOverriddenGameModes = in.createIntArray();
mIsDownscalingAllowed = in.readBoolean();
mIsFpsOverrideAllowed = in.readBoolean();
mConfigMap = new ArrayMap<>();
@@ -198,8 +198,8 @@ public final class GameModeInfo implements Parcelable {
* Gets the collection of {@link GameManager.GameMode} that can be applied to the game.
* <p>
* Available games include all game modes that are either supported by the OEM in device
- * config, or opted in by the game developers in game mode config XML, plus the default enabled
- * modes for any game including {@link GameManager#GAME_MODE_STANDARD} and
+ * config, or overridden by the game developers in game mode config XML, plus the default
+ * enabled modes for any game including {@link GameManager#GAME_MODE_STANDARD} and
* {@link GameManager#GAME_MODE_CUSTOM}.
* <p>
* Also see {@link GameModeInfo}.
@@ -210,19 +210,19 @@ public final class GameModeInfo implements Parcelable {
}
/**
- * Gets the collection of {@link GameManager.GameMode} that are opted in by the game.
+ * Gets the collection of {@link GameManager.GameMode} that are overridden by the game.
* <p>
* Also see {@link GameModeInfo}.
*/
@NonNull
- public @GameManager.GameMode int[] getOptedInGameModes() {
- return Arrays.copyOf(mOptedInGameModes, mOptedInGameModes.length);
+ public @GameManager.GameMode int[] getOverriddenGameModes() {
+ return Arrays.copyOf(mOverriddenGameModes, mOverriddenGameModes.length);
}
/**
* Gets the current game mode configuration of a game mode.
* <p>
- * The game mode can be null if it's opted in by the game itself, or not configured in device
+ * The game mode can be null if it's overridden by the game itself, or not configured in device
* config nor set by the user as custom game mode configuration.
*/
public @Nullable GameModeConfiguration getGameModeConfiguration(
@@ -250,7 +250,7 @@ public final class GameModeInfo implements Parcelable {
private final @GameManager.GameMode int[] mAvailableGameModes;
- private final @GameManager.GameMode int[] mOptedInGameModes;
+ private final @GameManager.GameMode int[] mOverriddenGameModes;
private final @GameManager.GameMode int mActiveGameMode;
private final boolean mIsDownscalingAllowed;
private final boolean mIsFpsOverrideAllowed;
@@ -265,7 +265,7 @@ public final class GameModeInfo implements Parcelable {
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeInt(mActiveGameMode);
dest.writeIntArray(mAvailableGameModes);
- dest.writeIntArray(mOptedInGameModes);
+ dest.writeIntArray(mOverriddenGameModes);
dest.writeBoolean(mIsDownscalingAllowed);
dest.writeBoolean(mIsFpsOverrideAllowed);
dest.writeMap(mConfigMap);
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 3edaabde5f2a..040111c78531 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -799,4 +799,7 @@ interface IActivityManager {
* <p>Typically used only by automotive builds when the vehicle has multiple displays.
*/
@nullable int[] getSecondaryDisplayIdsForStartingBackgroundUsers();
+
+ /** Returns if the service is a short-service is still "alive" and past the timeout. */
+ boolean shouldServiceTimeOut(in ComponentName className, in IBinder token);
}
diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl
index 595c7f7ab008..3984fee19203 100644
--- a/core/java/android/app/IApplicationThread.aidl
+++ b/core/java/android/app/IApplicationThread.aidl
@@ -20,6 +20,7 @@ import android.app.ContentProviderHolder;
import android.app.IInstrumentationWatcher;
import android.app.IUiAutomationConnection;
import android.app.ProfilerInfo;
+import android.app.ReceiverInfo;
import android.app.ResultInfo;
import android.app.servertransaction.ClientTransaction;
import android.content.AutofillOptions;
@@ -66,6 +67,9 @@ oneway interface IApplicationThread {
in CompatibilityInfo compatInfo,
int resultCode, in String data, in Bundle extras, boolean sync,
int sendingUser, int processState);
+
+ void scheduleReceiverList(in List<ReceiverInfo> info);
+
@UnsupportedAppUsage
void scheduleCreateService(IBinder token, in ServiceInfo info,
in CompatibilityInfo compatInfo, int processState);
diff --git a/core/java/android/app/IUiAutomationConnection.aidl b/core/java/android/app/IUiAutomationConnection.aidl
index 623af5f90749..fbb0748fc01f 100644
--- a/core/java/android/app/IUiAutomationConnection.aidl
+++ b/core/java/android/app/IUiAutomationConnection.aidl
@@ -40,6 +40,7 @@ interface IUiAutomationConnection {
void connect(IAccessibilityServiceClient client, int flags);
void disconnect();
boolean injectInputEvent(in InputEvent event, boolean sync, boolean waitForAnimations);
+ void injectInputEventToInputFilter(in InputEvent event);
void syncInputTransactions(boolean waitForAnimations);
boolean setRotation(int rotation);
Bitmap takeScreenshot(in Rect crop);
diff --git a/core/java/android/app/ReceiverInfo.aidl b/core/java/android/app/ReceiverInfo.aidl
new file mode 100644
index 000000000000..d90eee704a7e
--- /dev/null
+++ b/core/java/android/app/ReceiverInfo.aidl
@@ -0,0 +1,60 @@
+/**
+ * Copyright (c) 2022, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+import android.content.IIntentReceiver;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.res.CompatibilityInfo;
+import android.os.Bundle;
+
+/**
+ * Collect the information needed for manifest and registered receivers into a single structure
+ * that can be the element of a list. All fields are already parcelable.
+ * @hide
+ */
+parcelable ReceiverInfo {
+ /**
+ * Fields common to registered and manifest receivers.
+ */
+ Intent intent;
+ String data;
+ Bundle extras;
+ int sendingUser;
+ int processState;
+ int resultCode;
+
+ /**
+ * True if this instance represents a registered receiver and false if this instance
+ * represents a manifest receiver.
+ */
+ boolean registered;
+
+ /**
+ * Fields used only for registered receivers.
+ */
+ IIntentReceiver receiver;
+ boolean ordered;
+ boolean sticky;
+
+ /**
+ * Fields used only for manifest receivers.
+ */
+ ActivityInfo activityInfo;
+ CompatibilityInfo compatInfo;
+ boolean sync;
+}
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index d275c8336251..7e6386e47494 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -41,6 +41,7 @@ import android.os.Trace;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.DisplayMetrics;
+import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.Pair;
import android.util.Slog;
@@ -51,7 +52,6 @@ import android.window.WindowContext;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.IndentingPrintWriter;
import java.io.IOException;
import java.io.PrintWriter;
@@ -173,6 +173,7 @@ public class ResourcesManager {
/**
* The ApkAssets that are being referenced in the wild that we can reuse.
+ * Used as a lock for itself as well.
*/
private final ArrayMap<ApkKey, WeakReference<ApkAssets>> mCachedApkAssets = new ArrayMap<>();
@@ -286,38 +287,43 @@ public class ResourcesManager {
* try as hard as possible to release any open FDs.
*/
public void invalidatePath(String path) {
+ final List<ResourcesImpl> implsToFlush = new ArrayList<>();
synchronized (mLock) {
- int count = 0;
-
for (int i = mResourceImpls.size() - 1; i >= 0; i--) {
final ResourcesKey key = mResourceImpls.keyAt(i);
if (key.isPathReferenced(path)) {
- ResourcesImpl impl = mResourceImpls.removeAt(i).get();
- if (impl != null) {
- impl.flushLayoutCache();
+ ResourcesImpl resImpl = mResourceImpls.removeAt(i).get();
+ if (resImpl != null) {
+ implsToFlush.add(resImpl);
}
- count++;
}
}
-
- Log.i(TAG, "Invalidated " + count + " asset managers that referenced " + path);
-
+ }
+ for (int i = 0; i < implsToFlush.size(); i++) {
+ implsToFlush.get(i).flushLayoutCache();
+ }
+ final List<ApkAssets> assetsToClose = new ArrayList<>();
+ synchronized (mCachedApkAssets) {
for (int i = mCachedApkAssets.size() - 1; i >= 0; i--) {
final ApkKey key = mCachedApkAssets.keyAt(i);
if (key.path.equals(path)) {
- WeakReference<ApkAssets> apkAssetsRef = mCachedApkAssets.removeAt(i);
- if (apkAssetsRef != null && apkAssetsRef.get() != null) {
- apkAssetsRef.get().close();
+ final WeakReference<ApkAssets> apkAssetsRef = mCachedApkAssets.removeAt(i);
+ final ApkAssets apkAssets = apkAssetsRef != null ? apkAssetsRef.get() : null;
+ if (apkAssets != null) {
+ assetsToClose.add(apkAssets);
}
}
}
}
+ for (int i = 0; i < assetsToClose.size(); i++) {
+ assetsToClose.get(i).close();
+ }
+ Log.i(TAG,
+ "Invalidated " + implsToFlush.size() + " asset managers that referenced " + path);
}
public Configuration getConfiguration() {
- synchronized (mLock) {
- return mResConfiguration;
- }
+ return mResConfiguration;
}
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
@@ -402,14 +408,12 @@ public class ResourcesManager {
* @param resources The {@link Resources} backing the display adjustments.
*/
public Display getAdjustedDisplay(final int displayId, Resources resources) {
- synchronized (mLock) {
- final DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance();
- if (dm == null) {
- // may be null early in system startup
- return null;
- }
- return dm.getCompatibleDisplay(displayId, resources);
+ final DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance();
+ if (dm == null) {
+ // may be null early in system startup
+ return null;
}
+ return dm.getCompatibleDisplay(displayId, resources);
}
/**
@@ -446,16 +450,14 @@ public class ResourcesManager {
ApkAssets apkAssets;
// Optimistically check if this ApkAssets exists somewhere else.
- synchronized (mLock) {
- final WeakReference<ApkAssets> apkAssetsRef = mCachedApkAssets.get(key);
- if (apkAssetsRef != null) {
- apkAssets = apkAssetsRef.get();
- if (apkAssets != null && apkAssets.isUpToDate()) {
- return apkAssets;
- } else {
- // Clean up the reference.
- mCachedApkAssets.remove(key);
- }
+ final WeakReference<ApkAssets> apkAssetsRef;
+ synchronized (mCachedApkAssets) {
+ apkAssetsRef = mCachedApkAssets.get(key);
+ }
+ if (apkAssetsRef != null) {
+ apkAssets = apkAssetsRef.get();
+ if (apkAssets != null && apkAssets.isUpToDate()) {
+ return apkAssets;
}
}
@@ -472,7 +474,7 @@ public class ResourcesManager {
apkAssets = ApkAssets.loadFromPath(key.path, flags);
}
- synchronized (mLock) {
+ synchronized (mCachedApkAssets) {
mCachedApkAssets.put(key, new WeakReference<>(apkAssets));
}
@@ -584,28 +586,33 @@ public class ResourcesManager {
* @hide
*/
public void dump(String prefix, PrintWriter printWriter) {
+ final int references;
+ final int resImpls;
synchronized (mLock) {
- IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, " ");
- for (int i = 0; i < prefix.length() / 2; i++) {
- pw.increaseIndent();
- }
-
- pw.println("ResourcesManager:");
- pw.increaseIndent();
- pw.print("total apks: ");
- pw.println(countLiveReferences(mCachedApkAssets.values()));
-
- pw.print("resources: ");
-
- int references = countLiveReferences(mResourceReferences);
+ int refs = countLiveReferences(mResourceReferences);
for (ActivityResources activityResources : mActivityResourceReferences.values()) {
- references += activityResources.countLiveReferences();
+ refs += activityResources.countLiveReferences();
}
- pw.println(references);
+ references = refs;
+ resImpls = countLiveReferences(mResourceImpls.values());
+ }
+ final int liveAssets;
+ synchronized (mCachedApkAssets) {
+ liveAssets = countLiveReferences(mCachedApkAssets.values());
+ }
- pw.print("resource impls: ");
- pw.println(countLiveReferences(mResourceImpls.values()));
+ final var pw = new IndentingPrintWriter(printWriter, " ");
+ for (int i = 0; i < prefix.length() / 2; i++) {
+ pw.increaseIndent();
}
+ pw.println("ResourcesManager:");
+ pw.increaseIndent();
+ pw.print("total apks: ");
+ pw.println(liveAssets);
+ pw.print("resources: ");
+ pw.println(references);
+ pw.print("resource impls: ");
+ pw.println(resImpls);
}
private Configuration generateConfig(@NonNull ResourcesKey key) {
diff --git a/core/java/android/app/Service.java b/core/java/android/app/Service.java
index 3a7d483c69c3..0ab96a9560ad 100644
--- a/core/java/android/app/Service.java
+++ b/core/java/android/app/Service.java
@@ -1119,10 +1119,21 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac
/** @hide */
public final void callOnTimeout(int startId) {
- // TODO(short-service): Do we need any check here, to avoid races?
- // e.g. if the service is already stopped, but ActivityThread.handleTimeoutService() is
- // already scheduled, then we'll call this method anyway. It should be doable to prevent
- // that if we keep track of startForeground, stopForeground, and onDestroy.
+ // Note, because all the service callbacks (and other similar callbacks, e.g. activity
+ // callbacks) are delivered using the main handler, it's possible the service is already
+ // stopped when before this method is called, so we do a double check here.
+ if (mToken == null) {
+ Log.w(TAG, "Service already destroyed, skipping onTimeout()");
+ return;
+ }
+ try {
+ if (!mActivityManager.shouldServiceTimeOut(
+ new ComponentName(this, mClassName), mToken)) {
+ Log.w(TAG, "Service no longer relevant, skipping onTimeout()");
+ return;
+ }
+ } catch (RemoteException ex) {
+ }
onTimeout(startId);
}
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index a035375f2f82..f63f406f847d 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -234,7 +234,10 @@ public class StatusBarManager {
/**
* Session flag for {@link #registerSessionListener} indicating the listener
- * is interested in sessions on the keygaurd
+ * is interested in sessions on the keygaurd.
+ * Keyguard Session Boundaries:
+ * START_SESSION: device starts going to sleep OR the keyguard is newly shown
+ * END_SESSION: device starts going to sleep OR keyguard is no longer showing
* @hide
*/
public static final int SESSION_KEYGUARD = 1 << 0;
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 762ac23b6efe..3730a6f9b428 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -51,6 +51,8 @@ import android.app.usage.StorageStatsManager;
import android.app.usage.UsageStatsManager;
import android.app.wallpapereffectsgeneration.IWallpaperEffectsGenerationManager;
import android.app.wallpapereffectsgeneration.WallpaperEffectsGenerationManager;
+import android.app.wearable.IWearableSensingManager;
+import android.app.wearable.WearableSensingManager;
import android.apphibernation.AppHibernationManager;
import android.appwidget.AppWidgetManager;
import android.bluetooth.BluetoothFrameworkInitializer;
@@ -1544,6 +1546,17 @@ public final class SystemServiceRegistry {
IAmbientContextManager.Stub.asInterface(iBinder);
return new AmbientContextManager(ctx.getOuterContext(), manager);
}});
+ registerService(Context.WEARABLE_SENSING_SERVICE, WearableSensingManager.class,
+ new CachedServiceFetcher<WearableSensingManager>() {
+ @Override
+ public WearableSensingManager createService(ContextImpl ctx)
+ throws ServiceNotFoundException {
+ IBinder iBinder = ServiceManager.getServiceOrThrow(
+ Context.WEARABLE_SENSING_SERVICE);
+ IWearableSensingManager manager =
+ IWearableSensingManager.Stub.asInterface(iBinder);
+ return new WearableSensingManager(ctx.getOuterContext(), manager);
+ }});
sInitializing = true;
try {
diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java
index 27180546e940..814f38b2f3e6 100644
--- a/core/java/android/app/UiAutomation.java
+++ b/core/java/android/app/UiAutomation.java
@@ -793,6 +793,24 @@ public final class UiAutomation {
}
/**
+ * Injects an arbitrary {@link InputEvent} to the accessibility input filter, for use in testing
+ * the accessibility input filter.
+ *
+ * Events injected to the input subsystem using the standard {@link #injectInputEvent} method
+ * skip the accessibility input filter to avoid feedback loops.
+ *
+ * @hide
+ */
+ @TestApi
+ public void injectInputEventToInputFilter(@NonNull InputEvent event) {
+ try {
+ mUiAutomationConnection.injectInputEventToInputFilter(event);
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error while injecting input event to input filter", re);
+ }
+ }
+
+ /**
* Sets the system settings values that control the scaling factor for animations. The scale
* controls the animation playback speed for animations that respect these settings. Animations
* that do not respect the settings values will not be affected by this function. A lower scale
diff --git a/core/java/android/app/UiAutomationConnection.java b/core/java/android/app/UiAutomationConnection.java
index 0201c126f997..3e4e7cb453e1 100644
--- a/core/java/android/app/UiAutomationConnection.java
+++ b/core/java/android/app/UiAutomationConnection.java
@@ -178,6 +178,11 @@ public final class UiAutomationConnection extends IUiAutomationConnection.Stub {
}
@Override
+ public void injectInputEventToInputFilter(InputEvent event) throws RemoteException {
+ mAccessibilityManager.injectInputEventToInputFilter(event);
+ }
+
+ @Override
public void syncInputTransactions(boolean waitForAnimations) {
synchronized (mLock) {
throwIfCalledByNotTrustedUidLocked();
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index f5d657c4b388..e8804328ef9d 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -807,6 +807,7 @@ public class WallpaperManager {
* is not able to access the wallpaper.
*/
@RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE)
+ @Nullable
public Drawable getDrawable() {
final ColorManagementProxy cmProxy = getColorManagementProxy();
Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, true, FLAG_SYSTEM, cmProxy);
@@ -819,6 +820,29 @@ public class WallpaperManager {
}
/**
+ * Retrieve the requested wallpaper; if
+ * no wallpaper is set, the requested built-in static wallpaper is returned.
+ * This is returned as an
+ * abstract Drawable that you can install in a View to display whatever
+ * wallpaper the user has currently set.
+ * <p>
+ * This method can return null if the requested wallpaper is not available, if
+ * wallpapers are not supported in the current user, or if the calling app is not
+ * permitted to access the requested wallpaper.
+ *
+ * @param which The {@code FLAG_*} identifier of a valid wallpaper type. Throws
+ * IllegalArgumentException if an invalid wallpaper is requested.
+ * @return Returns a Drawable object that will draw the requested wallpaper,
+ * or {@code null} if the requested wallpaper does not exist or if the calling application
+ * is not able to access the wallpaper.
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE)
+ @Nullable
+ public Drawable getDrawable(@SetWallpaperFlags int which) {
+ return getDrawable();
+ }
+ /**
* Obtain a drawable for the built-in static system wallpaper.
*/
public Drawable getBuiltInDrawable() {
@@ -1037,6 +1061,7 @@ public class WallpaperManager {
* @return Returns a Drawable object that will draw the wallpaper or a
* null pointer if these is none.
*/
+ @Nullable
public Drawable peekDrawable() {
final ColorManagementProxy cmProxy = getColorManagementProxy();
Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, false, FLAG_SYSTEM, cmProxy);
@@ -1049,6 +1074,23 @@ public class WallpaperManager {
}
/**
+ * Retrieve the requested wallpaper; if there is no wallpaper set,
+ * a null pointer is returned. This is returned as an
+ * abstract Drawable that you can install in a View to display whatever
+ * wallpaper the user has currently set.
+ *
+ * @param which The {@code FLAG_*} identifier of a valid wallpaper type. Throws
+ * IllegalArgumentException if an invalid wallpaper is requested.
+ * @return Returns a Drawable object that will draw the wallpaper or a null pointer if these
+ * is none.
+ * @hide
+ */
+ @Nullable
+ public Drawable peekDrawable(@SetWallpaperFlags int which) {
+ return peekDrawable();
+ }
+
+ /**
* Like {@link #getDrawable()}, but the returned Drawable has a number
* of limitations to reduce its overhead as much as possible. It will
* never scale the wallpaper (only centering it if the requested bounds
@@ -1062,6 +1104,7 @@ public class WallpaperManager {
* @return Returns a Drawable object that will draw the wallpaper.
*/
@RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE)
+ @Nullable
public Drawable getFastDrawable() {
final ColorManagementProxy cmProxy = getColorManagementProxy();
Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, true, FLAG_SYSTEM, cmProxy);
@@ -1072,6 +1115,28 @@ public class WallpaperManager {
}
/**
+ * Like {@link #getFastDrawable(int)}, but the returned Drawable has a number
+ * of limitations to reduce its overhead as much as possible. It will
+ * never scale the wallpaper (only centering it if the requested bounds
+ * do match the bitmap bounds, which should not be typical), doesn't
+ * allow setting an alpha, color filter, or other attributes, etc. The
+ * bounds of the returned drawable will be initialized to the same bounds
+ * as the wallpaper, so normally you will not need to touch it. The
+ * drawable also assumes that it will be used in a context running in
+ * the same density as the screen (not in density compatibility mode).
+ *
+ * @param which The {@code FLAG_*} identifier of a valid wallpaper type. Throws
+ * IllegalArgumentException if an invalid wallpaper is requested.
+ * @return Returns a Drawable object that will draw the wallpaper.
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE)
+ @Nullable
+ public Drawable getFastDrawable(@SetWallpaperFlags int which) {
+ return getFastDrawable();
+ }
+
+ /**
* Like {@link #getFastDrawable()}, but if there is no wallpaper set,
* a null pointer is returned.
*
@@ -1079,6 +1144,7 @@ public class WallpaperManager {
* wallpaper or a null pointer if these is none.
*/
@RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE)
+ @Nullable
public Drawable peekFastDrawable() {
final ColorManagementProxy cmProxy = getColorManagementProxy();
Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, false, FLAG_SYSTEM, cmProxy);
@@ -1089,6 +1155,22 @@ public class WallpaperManager {
}
/**
+ * Like {@link #getFastDrawable()}, but if there is no wallpaper set,
+ * a null pointer is returned.
+ *
+ * @param which The {@code FLAG_*} identifier of a valid wallpaper type. Throws
+ * IllegalArgumentException if an invalid wallpaper is requested.
+ * @return Returns an optimized Drawable object that will draw the
+ * wallpaper or a null pointer if these is none.
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE)
+ @Nullable
+ public Drawable peekFastDrawable(@SetWallpaperFlags int which) {
+ return peekFastDrawable();
+ }
+
+ /**
* Whether the wallpaper supports Wide Color Gamut or not.
* @param which The wallpaper whose image file is to be retrieved. Must be a single
* defined kind of wallpaper, either {@link #FLAG_SYSTEM} or {@link #FLAG_LOCK}.
diff --git a/core/java/android/app/admin/DevicePolicyCache.java b/core/java/android/app/admin/DevicePolicyCache.java
index da6237513562..4a329a9c4891 100644
--- a/core/java/android/app/admin/DevicePolicyCache.java
+++ b/core/java/android/app/admin/DevicePolicyCache.java
@@ -61,7 +61,6 @@ public abstract class DevicePolicyCache {
*/
public abstract boolean canAdminGrantSensorsPermissionsForUser(@UserIdInt int userHandle);
-
/**
* Empty implementation.
*/
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index ecea1bb9ac8e..dd82bc1bc916 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -165,6 +165,12 @@ import java.util.function.Consumer;
@SuppressLint("UseIcu")
public class DevicePolicyManager {
+ /** @hide */
+ public static final String DEPRECATE_USERMANAGERINTERNAL_DEVICEPOLICY_FLAG =
+ "deprecate_usermanagerinternal_devicepolicy";
+ /** @hide */
+ public static final boolean DEPRECATE_USERMANAGERINTERNAL_DEVICEPOLICY_DEFAULT = false;
+
private static String TAG = "DevicePolicyManager";
private final Context mContext;
@@ -14449,7 +14455,9 @@ public class DevicePolicyManager {
* @throws SecurityException if {@code admin} is not a profile owner
*
* @see #getCrossProfileCalendarPackages(ComponentName)
+ * @deprecated Use {@link #setCrossProfilePackages(ComponentName, Set)}.
*/
+ @Deprecated
public void setCrossProfileCalendarPackages(@NonNull ComponentName admin,
@Nullable Set<String> packageNames) {
throwIfParentInstance("setCrossProfileCalendarPackages");
@@ -14475,7 +14483,9 @@ public class DevicePolicyManager {
* @throws SecurityException if {@code admin} is not a profile owner
*
* @see #setCrossProfileCalendarPackages(ComponentName, Set)
+ * @deprecated Use {@link #setCrossProfilePackages(ComponentName, Set)}.
*/
+ @Deprecated
public @Nullable Set<String> getCrossProfileCalendarPackages(@NonNull ComponentName admin) {
throwIfParentInstance("getCrossProfileCalendarPackages");
if (mService != null) {
diff --git a/core/java/android/app/admin/DevicePolicyManagerInternal.java b/core/java/android/app/admin/DevicePolicyManagerInternal.java
index b6f09167580f..840f3a3c6bb8 100644
--- a/core/java/android/app/admin/DevicePolicyManagerInternal.java
+++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java
@@ -269,4 +269,14 @@ public abstract class DevicePolicyManagerInternal {
* {@link #supportsResetOp(int)} is true.
*/
public abstract void resetOp(int op, String packageName, @UserIdInt int userId);
+
+ /**
+ * Returns whether new "turn off work" behavior is enabled via feature flag.
+ */
+ public abstract boolean isKeepProfilesRunningEnabled();
+
+ /**
+ * True if either the entire device or the user is organization managed.
+ */
+ public abstract boolean isUserOrganizationManaged(@UserIdInt int userId);
}
diff --git a/core/java/android/app/admin/DeviceStateCache.java b/core/java/android/app/admin/DeviceStateCache.java
index 7619aa2b7473..d1d130d88a39 100644
--- a/core/java/android/app/admin/DeviceStateCache.java
+++ b/core/java/android/app/admin/DeviceStateCache.java
@@ -15,6 +15,8 @@
*/
package android.app.admin;
+import android.annotation.UserIdInt;
+
import com.android.server.LocalServices;
/**
@@ -43,6 +45,11 @@ public abstract class DeviceStateCache {
public abstract boolean isDeviceProvisioned();
/**
+ * True if either the entire device or the user is organization managed.
+ */
+ public abstract boolean isUserOrganizationManaged(@UserIdInt int userHandle);
+
+ /**
* Empty implementation.
*/
private static class EmptyDeviceStateCache extends DeviceStateCache {
@@ -52,5 +59,10 @@ public abstract class DeviceStateCache {
public boolean isDeviceProvisioned() {
return false;
}
+
+ @Override
+ public boolean isUserOrganizationManaged(int userHandle) {
+ return false;
+ }
}
}
diff --git a/core/java/android/app/backup/BackupManager.java b/core/java/android/app/backup/BackupManager.java
index 378020f5387d..7255c3e0813f 100644
--- a/core/java/android/app/backup/BackupManager.java
+++ b/core/java/android/app/backup/BackupManager.java
@@ -1018,6 +1018,29 @@ public class BackupManager {
}
}
+ /**
+ * Get an instance of {@link BackupRestoreEventLogger} to report B&R related events during an
+ * ongoing backup or restore operation.
+ *
+ * @param backupAgent the agent currently running a B&R operation.
+ *
+ * @return an instance of {@code BackupRestoreEventLogger} or {@code null} if the agent has not
+ * finished initialisation, i.e. {@link BackupAgent#onCreate()} has not been called yet.
+ * @throws IllegalStateException if called before the agent has finished initialisation.
+ *
+ * @hide
+ */
+ @NonNull
+ @SystemApi
+ public BackupRestoreEventLogger getBackupRestoreEventLogger(@NonNull BackupAgent backupAgent) {
+ BackupRestoreEventLogger logger = backupAgent.getBackupRestoreEventLogger();
+ if (logger == null) {
+ throw new IllegalStateException("Attempting to get logger on an uninitialised "
+ + "BackupAgent");
+ }
+ return backupAgent.getBackupRestoreEventLogger();
+ }
+
/*
* We wrap incoming binder calls with a private class implementation that
* redirects them into main-thread actions. This serializes the backup
diff --git a/core/java/android/app/backup/BackupRestoreEventLogger.java b/core/java/android/app/backup/BackupRestoreEventLogger.java
index f89283377920..5805826ef123 100644
--- a/core/java/android/app/backup/BackupRestoreEventLogger.java
+++ b/core/java/android/app/backup/BackupRestoreEventLogger.java
@@ -18,6 +18,7 @@ package android.app.backup;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
@@ -35,7 +36,6 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
-// TODO(b/244436184): Make this @SystemApi
/**
* Class to log B&R stats for each data type that is backed up and restored by the calling app.
*
@@ -46,12 +46,15 @@ import java.util.Map;
*
* @hide
*/
+@SystemApi
public class BackupRestoreEventLogger {
private static final String TAG = "BackupRestoreEventLogger";
/**
* Max number of unique data types for which an instance of this logger can store info. Attempts
* to use more distinct data type values will be rejected.
+ *
+ * @hide
*/
public static final int DATA_TYPES_ALLOWED = 15;
@@ -301,7 +304,7 @@ public class BackupRestoreEventLogger {
/**
* Encapsulate logging results for a single data type.
*/
- public static class DataTypeResult implements Parcelable {
+ public static final class DataTypeResult implements Parcelable {
@BackupRestoreDataType
private final String mDataType;
private int mSuccessCount;
@@ -309,7 +312,7 @@ public class BackupRestoreEventLogger {
private final Map<String, Integer> mErrors = new HashMap<>();
private byte[] mMetadataHash;
- public DataTypeResult(String dataType) {
+ public DataTypeResult(@NonNull String dataType) {
mDataType = dataType;
}
@@ -358,7 +361,7 @@ public class BackupRestoreEventLogger {
}
@Override
- public void writeToParcel(Parcel dest, int flags) {
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeString(mDataType);
dest.writeInt(mSuccessCount);
@@ -374,6 +377,7 @@ public class BackupRestoreEventLogger {
dest.writeByteArray(mMetadataHash);
}
+ @NonNull
public static final Parcelable.Creator<DataTypeResult> CREATOR =
new Parcelable.Creator<>() {
public DataTypeResult createFromParcel(Parcel in) {
diff --git a/core/java/android/companion/virtual/IVirtualDevice.aidl b/core/java/android/companion/virtual/IVirtualDevice.aidl
index 0837d85bb91c..5c47ea2aa3f0 100644
--- a/core/java/android/companion/virtual/IVirtualDevice.aidl
+++ b/core/java/android/companion/virtual/IVirtualDevice.aidl
@@ -24,11 +24,15 @@ import android.companion.virtual.sensor.VirtualSensorConfig;
import android.companion.virtual.sensor.VirtualSensorEvent;
import android.graphics.Point;
import android.graphics.PointF;
+import android.hardware.input.VirtualDpadConfig;
+import android.hardware.input.VirtualKeyboardConfig;
import android.hardware.input.VirtualKeyEvent;
import android.hardware.input.VirtualMouseButtonEvent;
+import android.hardware.input.VirtualMouseConfig;
import android.hardware.input.VirtualMouseRelativeEvent;
import android.hardware.input.VirtualMouseScrollEvent;
import android.hardware.input.VirtualTouchEvent;
+import android.hardware.input.VirtualTouchscreenConfig;
import android.os.ResultReceiver;
/**
@@ -64,32 +68,22 @@ interface IVirtualDevice {
IAudioConfigChangedCallback configChangedCallback);
void onAudioSessionEnded();
-
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)")
void createVirtualDpad(
- int displayId,
- String inputDeviceName,
- int vendorId,
- int productId,
+ in VirtualDpadConfig config,
IBinder token);
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)")
void createVirtualKeyboard(
- int displayId,
- String inputDeviceName,
- int vendorId,
- int productId,
+ in VirtualKeyboardConfig config,
IBinder token);
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)")
void createVirtualMouse(
- int displayId,
- String inputDeviceName,
- int vendorId,
- int productId,
+ in VirtualMouseConfig config,
IBinder token);
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)")
void createVirtualTouchscreen(
- int displayId,
- String inputDeviceName,
- int vendorId,
- int productId,
- IBinder token,
- in Point screenSize);
+ in VirtualTouchscreenConfig config,
+ IBinder token);
void unregisterInputDevice(IBinder token);
int getInputDeviceId(IBinder token);
boolean sendDpadKeyEvent(IBinder token, in VirtualKeyEvent event);
diff --git a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
index 7d6336a225bd..6e784b2e607d 100644
--- a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
+++ b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
@@ -51,6 +51,11 @@ interface IVirtualDeviceManager {
*/
List<VirtualDevice> getVirtualDevices();
+ /**
+ * Returns the ID of the device which owns the display with the given ID.
+ */
+ int getDeviceIdForDisplayId(int displayId);
+
/**
* Returns the device policy for the given virtual device and policy type.
*/
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index 01b42bfa661c..109f4b1931f9 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -41,9 +41,13 @@ import android.hardware.display.IVirtualDisplayCallback;
import android.hardware.display.VirtualDisplay;
import android.hardware.display.VirtualDisplayConfig;
import android.hardware.input.VirtualDpad;
+import android.hardware.input.VirtualDpadConfig;
import android.hardware.input.VirtualKeyboard;
+import android.hardware.input.VirtualKeyboardConfig;
import android.hardware.input.VirtualMouse;
+import android.hardware.input.VirtualMouseConfig;
import android.hardware.input.VirtualTouchscreen;
+import android.hardware.input.VirtualTouchscreenConfig;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
@@ -71,7 +75,6 @@ import java.util.function.IntConsumer;
@SystemService(Context.VIRTUAL_DEVICE_SERVICE)
public final class VirtualDeviceManager {
- private static final boolean DEBUG = false;
private static final String TAG = "VirtualDeviceManager";
private static final int DEFAULT_VIRTUAL_DISPLAY_FLAGS =
@@ -80,7 +83,6 @@ public final class VirtualDeviceManager {
| DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY
| DisplayManager.VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL
| DisplayManager.VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH
- | DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP
| DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_FOCUS;
/**
@@ -229,6 +231,23 @@ public final class VirtualDeviceManager {
}
/**
+ * Returns the ID of the device which owns the display with the given ID.
+ *
+ * @hide
+ */
+ public int getDeviceIdForDisplayId(int displayId) {
+ if (mService == null) {
+ Log.w(TAG, "Failed to retrieve virtual devices; no virtual device manager service.");
+ return DEFAULT_DEVICE_ID;
+ }
+ try {
+ return mService.getDeviceIdForDisplayId(displayId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* A virtual device has its own virtual display, audio output, microphone, and camera etc. The
* creator of a virtual device can take the output from the virtual display and stream it over
* to another device, and inject input events that are received from the remote device.
@@ -310,6 +329,18 @@ public final class VirtualDeviceManager {
}
/**
+ * @return A new Context bound to this device. This is a convenience method equivalent to
+ * calling {@link Context#createDeviceContext(int)} with the device id of this device.
+ */
+ public @NonNull Context createContext() {
+ try {
+ return mContext.createDeviceContext(mVirtualDevice.getDeviceId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Returns this device's sensor with the given type and name, if any.
*
* @see VirtualDeviceParams.Builder#addVirtualSensorConfig
@@ -416,7 +447,7 @@ public final class VirtualDeviceManager {
* @param densityDpi The density of the virtual display in dpi, must be greater than 0.
* @param displayCategories The categories of the virtual display, indicating the type of
* activities allowed to run on the display. Activities can declare their type using
- * {@link android.content.pm.ActivityInfo#targetDisplayCategory}.
+ * {@link android.content.pm.ActivityInfo#requiredDisplayCategory}.
* @param surface The surface to which the content of the virtual display should
* be rendered, or null if there is none initially. The surface can also be set later using
* {@link VirtualDisplay#setSurface(Surface)}.
@@ -500,23 +531,15 @@ public final class VirtualDeviceManager {
/**
* Creates a virtual dpad.
*
- * @param display the display that the events inputted through this device should target
- * @param inputDeviceName the name to call this input device
- * @param vendorId the PCI vendor id
- * @param productId the product id, as defined by the vendor
+ * @param config the configurations of the virtual Dpad.
*/
@RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
@NonNull
- public VirtualDpad createVirtualDpad(
- @NonNull VirtualDisplay display,
- @NonNull String inputDeviceName,
- int vendorId,
- int productId) {
+ public VirtualDpad createVirtualDpad(@NonNull VirtualDpadConfig config) {
try {
final IBinder token = new Binder(
- "android.hardware.input.VirtualDpad:" + inputDeviceName);
- mVirtualDevice.createVirtualDpad(display.getDisplay().getDisplayId(),
- inputDeviceName, vendorId, productId, token);
+ "android.hardware.input.VirtualDpad:" + config.getInputDeviceName());
+ mVirtualDevice.createVirtualDpad(config, token);
return new VirtualDpad(mVirtualDevice, token);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -526,23 +549,15 @@ public final class VirtualDeviceManager {
/**
* Creates a virtual keyboard.
*
- * @param display the display that the events inputted through this device should target
- * @param inputDeviceName the name to call this input device
- * @param vendorId the PCI vendor id
- * @param productId the product id, as defined by the vendor
+ * @param config the configurations of the virtual keyboard.
*/
@RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
@NonNull
- public VirtualKeyboard createVirtualKeyboard(
- @NonNull VirtualDisplay display,
- @NonNull String inputDeviceName,
- int vendorId,
- int productId) {
+ public VirtualKeyboard createVirtualKeyboard(@NonNull VirtualKeyboardConfig config) {
try {
final IBinder token = new Binder(
- "android.hardware.input.VirtualKeyboard:" + inputDeviceName);
- mVirtualDevice.createVirtualKeyboard(display.getDisplay().getDisplayId(),
- inputDeviceName, vendorId, productId, token);
+ "android.hardware.input.VirtualKeyboard:" + config.getInputDeviceName());
+ mVirtualDevice.createVirtualKeyboard(config, token);
return new VirtualKeyboard(mVirtualDevice, token);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -550,25 +565,43 @@ public final class VirtualDeviceManager {
}
/**
- * Creates a virtual mouse.
+ * Creates a virtual keyboard.
*
- * @param display the display that the events inputted through this device should target
+ * @param display the display that the events inputted through this device should
+ * target
* @param inputDeviceName the name to call this input device
- * @param vendorId the PCI vendor id
- * @param productId the product id, as defined by the vendor
+ * @param vendorId the PCI vendor id
+ * @param productId the product id, as defined by the vendor
+ * @see #createVirtualKeyboard(VirtualKeyboardConfig config)
+ * @deprecated Use {@link #createVirtualKeyboard(VirtualKeyboardConfig config)} instead
*/
+ @Deprecated
@RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
@NonNull
- public VirtualMouse createVirtualMouse(
- @NonNull VirtualDisplay display,
- @NonNull String inputDeviceName,
- int vendorId,
- int productId) {
+ public VirtualKeyboard createVirtualKeyboard(@NonNull VirtualDisplay display,
+ @NonNull String inputDeviceName, int vendorId, int productId) {
+ VirtualKeyboardConfig keyboardConfig =
+ new VirtualKeyboardConfig.Builder()
+ .setVendorId(vendorId)
+ .setProductId(productId)
+ .setInputDeviceName(inputDeviceName)
+ .setAssociatedDisplayId(display.getDisplay().getDisplayId())
+ .build();
+ return createVirtualKeyboard(keyboardConfig);
+ }
+
+ /**
+ * Creates a virtual mouse.
+ *
+ * @param config the configurations of the virtual mouse.
+ */
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ @NonNull
+ public VirtualMouse createVirtualMouse(@NonNull VirtualMouseConfig config) {
try {
final IBinder token = new Binder(
- "android.hardware.input.VirtualMouse:" + inputDeviceName);
- mVirtualDevice.createVirtualMouse(display.getDisplay().getDisplayId(),
- inputDeviceName, vendorId, productId, token);
+ "android.hardware.input.VirtualMouse:" + config.getInputDeviceName());
+ mVirtualDevice.createVirtualMouse(config, token);
return new VirtualMouse(mVirtualDevice, token);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -576,27 +609,45 @@ public final class VirtualDeviceManager {
}
/**
- * Creates a virtual touchscreen.
+ * Creates a virtual mouse.
*
- * @param display the display that the events inputted through this device should target
+ * @param display the display that the events inputted through this device should
+ * target
* @param inputDeviceName the name to call this input device
- * @param vendorId the PCI vendor id
- * @param productId the product id, as defined by the vendor
+ * @param vendorId the PCI vendor id
+ * @param productId the product id, as defined by the vendor
+ * @see #createVirtualMouse(VirtualMouseConfig config)
+ * @deprecated Use {@link #createVirtualMouse(VirtualMouseConfig config)} instead
+ * *
+ */
+ @Deprecated
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ @NonNull
+ public VirtualMouse createVirtualMouse(@NonNull VirtualDisplay display,
+ @NonNull String inputDeviceName, int vendorId, int productId) {
+ VirtualMouseConfig mouseConfig =
+ new VirtualMouseConfig.Builder()
+ .setVendorId(vendorId)
+ .setProductId(productId)
+ .setInputDeviceName(inputDeviceName)
+ .setAssociatedDisplayId(display.getDisplay().getDisplayId())
+ .build();
+ return createVirtualMouse(mouseConfig);
+ }
+
+ /**
+ * Creates a virtual touchscreen.
+ *
+ * @param config the configurations of the virtual touchscreen.
*/
@RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
@NonNull
public VirtualTouchscreen createVirtualTouchscreen(
- @NonNull VirtualDisplay display,
- @NonNull String inputDeviceName,
- int vendorId,
- int productId) {
+ @NonNull VirtualTouchscreenConfig config) {
try {
final IBinder token = new Binder(
- "android.hardware.input.VirtualTouchscreen:" + inputDeviceName);
- final Point size = new Point();
- display.getDisplay().getSize(size);
- mVirtualDevice.createVirtualTouchscreen(display.getDisplay().getDisplayId(),
- inputDeviceName, vendorId, productId, token, size);
+ "android.hardware.input.VirtualTouchscreen:" + config.getInputDeviceName());
+ mVirtualDevice.createVirtualTouchscreen(config, token);
return new VirtualTouchscreen(mVirtualDevice, token);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -604,6 +655,37 @@ public final class VirtualDeviceManager {
}
/**
+ * Creates a virtual touchscreen.
+ *
+ * @param display the display that the events inputted through this device should
+ * target
+ * @param inputDeviceName the name to call this input device
+ * @param vendorId the PCI vendor id
+ * @param productId the product id, as defined by the vendor
+ * @see #createVirtualTouchscreen(VirtualTouchscreenConfig config)
+ * @deprecated Use {@link #createVirtualTouchscreen(VirtualTouchscreenConfig config)}
+ * instead
+ */
+ @Deprecated
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ @NonNull
+ public VirtualTouchscreen createVirtualTouchscreen(@NonNull VirtualDisplay display,
+ @NonNull String inputDeviceName, int vendorId, int productId) {
+ final Point size = new Point();
+ display.getDisplay().getSize(size);
+ VirtualTouchscreenConfig touchscreenConfig =
+ new VirtualTouchscreenConfig.Builder()
+ .setVendorId(vendorId)
+ .setProductId(productId)
+ .setInputDeviceName(inputDeviceName)
+ .setAssociatedDisplayId(display.getDisplay().getDisplayId())
+ .setWidthInPixels(size.x)
+ .setHeightInPixels(size.y)
+ .build();
+ return createVirtualTouchscreen(touchscreenConfig);
+ }
+
+ /**
* Creates a VirtualAudioDevice, capable of recording audio emanating from this device,
* or injecting audio from another device.
*
diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java
index 1cbe910d131a..be77f2b7e0f5 100644
--- a/core/java/android/companion/virtual/VirtualDeviceParams.java
+++ b/core/java/android/companion/virtual/VirtualDeviceParams.java
@@ -147,6 +147,19 @@ public final class VirtualDeviceParams implements Parcelable {
*/
public static final int POLICY_TYPE_SENSORS = 0;
+ /** @hide */
+ @IntDef(flag = true, prefix = "RECENTS_POLICY_",
+ value = {RECENTS_POLICY_ALLOW_IN_HOST_DEVICE_RECENTS})
+ @Retention(RetentionPolicy.SOURCE)
+ @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
+ public @interface RecentsPolicy {}
+
+ /**
+ * If set, activities launched on this virtual device are allowed to appear in the host device
+ * of the recently launched activities list.
+ */
+ public static final int RECENTS_POLICY_ALLOW_IN_HOST_DEVICE_RECENTS = 1 << 0;
+
private final int mLockState;
@NonNull private final ArraySet<UserHandle> mUsersWithMatchingAccounts;
@NonNull private final ArraySet<ComponentName> mAllowedCrossTaskNavigations;
@@ -161,6 +174,8 @@ public final class VirtualDeviceParams implements Parcelable {
// Mapping of @PolicyType to @DevicePolicy
@NonNull private final SparseIntArray mDevicePolicies;
@NonNull private final List<VirtualSensorConfig> mVirtualSensorConfigs;
+ @RecentsPolicy
+ private final int mDefaultRecentsPolicy;
private VirtualDeviceParams(
@LockState int lockState,
@@ -173,7 +188,8 @@ public final class VirtualDeviceParams implements Parcelable {
@ActivityPolicy int defaultActivityPolicy,
@Nullable String name,
@NonNull SparseIntArray devicePolicies,
- @NonNull List<VirtualSensorConfig> virtualSensorConfigs) {
+ @NonNull List<VirtualSensorConfig> virtualSensorConfigs,
+ @RecentsPolicy int defaultRecentsPolicy) {
mLockState = lockState;
mUsersWithMatchingAccounts =
new ArraySet<>(Objects.requireNonNull(usersWithMatchingAccounts));
@@ -188,6 +204,7 @@ public final class VirtualDeviceParams implements Parcelable {
mName = name;
mDevicePolicies = Objects.requireNonNull(devicePolicies);
mVirtualSensorConfigs = Objects.requireNonNull(virtualSensorConfigs);
+ mDefaultRecentsPolicy = defaultRecentsPolicy;
}
@SuppressWarnings("unchecked")
@@ -204,6 +221,7 @@ public final class VirtualDeviceParams implements Parcelable {
mDevicePolicies = parcel.readSparseIntArray();
mVirtualSensorConfigs = new ArrayList<>();
parcel.readTypedList(mVirtualSensorConfigs, VirtualSensorConfig.CREATOR);
+ mDefaultRecentsPolicy = parcel.readInt();
}
/**
@@ -328,6 +346,16 @@ public final class VirtualDeviceParams implements Parcelable {
return mVirtualSensorConfigs;
}
+ /**
+ * Returns the policy of how to handle activities in recents.
+ *
+ * @see RecentsPolicy
+ */
+ @RecentsPolicy
+ public int getDefaultRecentsPolicy() {
+ return mDefaultRecentsPolicy;
+ }
+
@Override
public int describeContents() {
return 0;
@@ -346,6 +374,7 @@ public final class VirtualDeviceParams implements Parcelable {
dest.writeString8(mName);
dest.writeSparseIntArray(mDevicePolicies);
dest.writeTypedList(mVirtualSensorConfigs);
+ dest.writeInt(mDefaultRecentsPolicy);
}
@Override
@@ -377,15 +406,17 @@ public final class VirtualDeviceParams implements Parcelable {
&& Objects.equals(mAllowedActivities, that.mAllowedActivities)
&& Objects.equals(mBlockedActivities, that.mBlockedActivities)
&& mDefaultActivityPolicy == that.mDefaultActivityPolicy
- && Objects.equals(mName, that.mName);
+ && Objects.equals(mName, that.mName)
+ && mDefaultRecentsPolicy == that.mDefaultRecentsPolicy;
}
@Override
public int hashCode() {
int hashCode = Objects.hash(
mLockState, mUsersWithMatchingAccounts, mAllowedCrossTaskNavigations,
- mBlockedCrossTaskNavigations, mDefaultNavigationPolicy, mAllowedActivities,
- mBlockedActivities, mDefaultActivityPolicy, mName, mDevicePolicies);
+ mBlockedCrossTaskNavigations, mDefaultNavigationPolicy, mAllowedActivities,
+ mBlockedActivities, mDefaultActivityPolicy, mName, mDevicePolicies,
+ mDefaultRecentsPolicy);
for (int i = 0; i < mDevicePolicies.size(); i++) {
hashCode = 31 * hashCode + mDevicePolicies.keyAt(i);
hashCode = 31 * hashCode + mDevicePolicies.valueAt(i);
@@ -407,6 +438,7 @@ public final class VirtualDeviceParams implements Parcelable {
+ " mDefaultActivityPolicy=" + mDefaultActivityPolicy
+ " mName=" + mName
+ " mDevicePolicies=" + mDevicePolicies
+ + " mDefaultRecentsPolicy=" + mDefaultRecentsPolicy
+ ")";
}
@@ -442,6 +474,7 @@ public final class VirtualDeviceParams implements Parcelable {
@Nullable private String mName;
@NonNull private SparseIntArray mDevicePolicies = new SparseIntArray();
@NonNull private List<VirtualSensorConfig> mVirtualSensorConfigs = new ArrayList<>();
+ private int mDefaultRecentsPolicy;
/**
* Sets the lock state of the device. The permission {@code ADD_ALWAYS_UNLOCKED_DISPLAY}
@@ -647,6 +680,17 @@ public final class VirtualDeviceParams implements Parcelable {
}
/**
+ * Sets the policy to indicate how activities are handled in recents.
+ *
+ * @param defaultRecentsPolicy A policy specifying how to handle activities in recents.
+ */
+ @NonNull
+ public Builder setDefaultRecentsPolicy(@RecentsPolicy int defaultRecentsPolicy) {
+ mDefaultRecentsPolicy = defaultRecentsPolicy;
+ return this;
+ }
+
+ /**
* Builds the {@link VirtualDeviceParams} instance.
*
* @throws IllegalArgumentException if there's mismatch between policy definition and
@@ -684,7 +728,8 @@ public final class VirtualDeviceParams implements Parcelable {
mDefaultActivityPolicy,
mName,
mDevicePolicies,
- mVirtualSensorConfigs);
+ mVirtualSensorConfigs,
+ mDefaultRecentsPolicy);
}
}
}
diff --git a/core/java/android/companion/virtual/sensor/VirtualSensor.java b/core/java/android/companion/virtual/sensor/VirtualSensor.java
index a184481bdf99..58a5387c5651 100644
--- a/core/java/android/companion/virtual/sensor/VirtualSensor.java
+++ b/core/java/android/companion/virtual/sensor/VirtualSensor.java
@@ -20,6 +20,7 @@ import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.companion.virtual.IVirtualDevice;
+import android.hardware.Sensor;
import android.os.IBinder;
import android.os.RemoteException;
@@ -68,8 +69,10 @@ public class VirtualSensor {
}
/**
- * Returns the
- * <a href="https://source.android.com/devices/sensors/sensor-types">type</a> of the sensor.
+ * Returns the type of the sensor.
+ *
+ * @see Sensor#getType()
+ * @see <a href="https://source.android.com/devices/sensors/sensor-types">Sensor types</a>
*/
public int getType() {
return mType;
@@ -87,7 +90,7 @@ public class VirtualSensor {
* Send a sensor event to the system.
*/
@RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
- public void sendSensorEvent(@NonNull VirtualSensorEvent event) {
+ public void sendEvent(@NonNull VirtualSensorEvent event) {
try {
mVirtualDevice.sendSensorEvent(mToken, event);
} catch (RemoteException e) {
diff --git a/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java b/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java
index 7982fa59daf3..eb2f9dde48db 100644
--- a/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java
+++ b/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java
@@ -23,6 +23,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
+import android.hardware.Sensor;
import android.os.Parcel;
import android.os.Parcelable;
@@ -77,8 +78,10 @@ public final class VirtualSensorConfig implements Parcelable {
}
/**
- * Returns the
- * <a href="https://source.android.com/devices/sensors/sensor-types">type</a> of the sensor.
+ * Returns the type of the sensor.
+ *
+ * @see Sensor#getType()
+ * @see <a href="https://source.android.com/devices/sensors/sensor-types">Sensor types</a>
*/
public int getType() {
return mType;
@@ -150,8 +153,7 @@ public final class VirtualSensorConfig implements Parcelable {
/**
* Creates a new builder.
*
- * @param type The
- * <a href="https://source.android.com/devices/sensors/sensor-types">type</a> of the sensor.
+ * @param type The type of the sensor, matching {@link Sensor#getType}.
* @param name The name of the sensor. Must be unique among all sensors with the same type
* that belong to the same virtual device.
*/
diff --git a/core/java/android/companion/virtual/sensor/VirtualSensorEvent.java b/core/java/android/companion/virtual/sensor/VirtualSensorEvent.java
index 8f8860ed5e6e..01b49750572d 100644
--- a/core/java/android/companion/virtual/sensor/VirtualSensorEvent.java
+++ b/core/java/android/companion/virtual/sensor/VirtualSensorEvent.java
@@ -18,6 +18,7 @@ package android.companion.virtual.sensor;
import android.annotation.NonNull;
import android.annotation.SystemApi;
+import android.hardware.Sensor;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.SystemClock;
@@ -60,9 +61,11 @@ public final class VirtualSensorEvent implements Parcelable {
}
/**
- * Returns the values of this sensor event. The length and contents depend on the
- * <a href="https://source.android.com/devices/sensors/sensor-types">sensor type</a>.
+ * Returns the values of this sensor event. The length and contents depend on the sensor type.
+ *
* @see android.hardware.SensorEvent#values
+ * @see Sensor#getType()
+ * @see <a href="https://source.android.com/devices/sensors/sensor-types">Sensor types</a>
*/
@NonNull
public float[] getValues() {
@@ -90,7 +93,9 @@ public final class VirtualSensorEvent implements Parcelable {
/**
* Creates a new builder.
- * @param values the values of the sensor event. @see android.hardware.SensorEvent#values
+ *
+ * @param values the values of the sensor event.
+ * @see android.hardware.SensorEvent#values
*/
public Builder(@NonNull float[] values) {
mValues = values;
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 9f9fd3ceb5f7..df5a1ed661cd 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -5937,6 +5937,14 @@ public abstract class Context {
public static final String FILE_INTEGRITY_SERVICE = "file_integrity";
/**
+ * Binder service for remote key provisioning.
+ *
+ * @see android.frameworks.rkp.IRemoteProvisioning
+ * @hide
+ */
+ public static final String REMOTE_PROVISIONING_SERVICE = "remote_provisioning";
+
+ /**
* Use with {@link #getSystemService(String)} to retrieve a
* {@link android.hardware.lights.LightsManager} for controlling device lights.
*
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 65b3ca42fc66..86a672f1d67e 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -5107,6 +5107,21 @@ public class Intent implements Parcelable, Cloneable {
public static final String ACTION_VIEW_LOCUS = "android.intent.action.VIEW_LOCUS";
/**
+ * Activity Action: Starts a note-taking activity that can be used to create a note. This action
+ * can be used to start an activity on the lock screen. Activity should ensure to appropriately
+ * handle privacy sensitive data and features when launched on the lock screen. See
+ * {@link android.app.KeyguardManager} for lock screen checks.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_CREATE_NOTE = "android.intent.action.CREATE_NOTE";
+
+ /**
+ * A boolean extra used with {@link #ACTION_CREATE_NOTE} indicating whether the launched
+ * note-taking activity should show a UI that is suitable to use with stylus input.
+ */
+ public static final String EXTRA_USE_STYLUS_MODE = "android.intent.extra.USE_STYLUS_MODE";
+
+ /**
* Broadcast Action: Sent to the integrity component when a package
* needs to be verified. The data contains the package URI along with other relevant
* information.
@@ -6109,6 +6124,14 @@ public class Intent implements Parcelable, Cloneable {
public static final String EXTRA_REPLACING = "android.intent.extra.REPLACING";
/**
+ * Used as a boolean extra field in {@link android.content.Intent#ACTION_PACKAGE_REMOVED}
+ * intents to indicate that this is a system update uninstall.
+ * @hide
+ */
+ public static final String EXTRA_SYSTEM_UPDATE_UNINSTALL =
+ "android.intent.extra.SYSTEM_UPDATE_UNINSTALL";
+
+ /**
* Used as an int extra field in {@link android.app.AlarmManager} pending intents
* to tell the application being invoked how many pending alarms are being
* delivered with the intent. For one-shot alarms this will always be 1.
diff --git a/core/java/android/content/om/OverlayInfo.java b/core/java/android/content/om/OverlayInfo.java
index c66f49cab088..a470de2db8fd 100644
--- a/core/java/android/content/om/OverlayInfo.java
+++ b/core/java/android/content/om/OverlayInfo.java
@@ -51,6 +51,7 @@ public final class OverlayInfo implements CriticalOverlayInfo, Parcelable {
STATE_ENABLED_IMMUTABLE,
// @Deprecated STATE_TARGET_IS_BEING_REPLACED,
STATE_OVERLAY_IS_BEING_REPLACED,
+ STATE_SYSTEM_UPDATE_UNINSTALL,
})
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@@ -128,6 +129,14 @@ public final class OverlayInfo implements CriticalOverlayInfo, Parcelable {
public static final int STATE_ENABLED_IMMUTABLE = 6;
/**
+ * The target package needs to be refreshed as a result of a system update uninstall, which
+ * must recalculate the state of overlays against the newly enabled system package, which may
+ * differ in resources/policy from the /data variant that was uninstalled.
+ * @hide
+ */
+ public static final int STATE_SYSTEM_UPDATE_UNINSTALL = 7;
+
+ /**
* Overlay category: theme.
* <p>
* Change how Android (including the status bar, dialogs, ...) looks.
diff --git a/core/java/android/content/om/TEST_MAPPING b/core/java/android/content/om/TEST_MAPPING
index 6185cf64ad12..a9aaf1a89504 100644
--- a/core/java/android/content/om/TEST_MAPPING
+++ b/core/java/android/content/om/TEST_MAPPING
@@ -12,6 +12,9 @@
"name": "OverlayDeviceTests"
},
{
+ "name": "SelfTargetingOverlayDeviceTests"
+ },
+ {
"name": "OverlayHostTests"
},
{
@@ -35,6 +38,9 @@
},
{
"include-filter": "android.content.om.cts"
+ },
+ {
+ "include-filter": "android.content.res.loader.cts"
}
]
}
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index fda4119aff99..dab57fda6106 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -221,21 +221,20 @@ public class ActivityInfo extends ComponentInfo implements Parcelable {
public String launchToken;
/**
- * Specifies the category of the target display the activity is expected to run on. Set from
- * the {@link android.R.attr#targetDisplayCategory} attribute. Upon creation, a virtual display
- * can specify which display categories it supports and one of the category must be present in
- * the activity's manifest to allow this activity to run. The default value is {@code null},
- * which indicates the activity does not belong to a restricted display category and thus can
- * only run on a display that didn't specify any display categories. Each activity can only
- * specify one category it targets to but a virtual display can support multiple restricted
- * categories.
- *
+ * Specifies the required display category of the activity. Set from the
+ * {@link android.R.attr#requiredDisplayCategory} attribute. Upon creation, a display can
+ * specify which display categories it supports and one of the category must be present
+ * in the {@code <activity>} element to allow this activity to run. The default value is
+ * {@code null}, which indicates the activity does not have a required display category and
+ * thus can only run on a display that didn't specify any display categories. Each activity
+ * can only specify one required category but a display can support multiple display categories.
+ * <p>
* This field should be formatted as a Java-language-style free form string(for example,
* com.google.automotive_entertainment), which may contain uppercase or lowercase letters ('A'
* through 'Z'), numbers, and underscores ('_') but may only start with letters.
*/
@Nullable
- public String targetDisplayCategory;
+ public String requiredDisplayCategory;
/**
* Activity can not be resized and always occupies the fullscreen area with all windows fully
@@ -1330,7 +1329,7 @@ public class ActivityInfo extends ComponentInfo implements Parcelable {
mMaxAspectRatio = orig.mMaxAspectRatio;
mMinAspectRatio = orig.mMinAspectRatio;
supportsSizeChanges = orig.supportsSizeChanges;
- targetDisplayCategory = orig.targetDisplayCategory;
+ requiredDisplayCategory = orig.requiredDisplayCategory;
}
/**
@@ -1669,8 +1668,8 @@ public class ActivityInfo extends ComponentInfo implements Parcelable {
if (mKnownActivityEmbeddingCerts != null) {
pw.println(prefix + "knownActivityEmbeddingCerts=" + mKnownActivityEmbeddingCerts);
}
- if (targetDisplayCategory != null) {
- pw.println(prefix + "targetDisplayCategory=" + targetDisplayCategory);
+ if (requiredDisplayCategory != null) {
+ pw.println(prefix + "requiredDisplayCategory=" + requiredDisplayCategory);
}
super.dumpBack(pw, prefix, dumpFlags);
}
@@ -1718,7 +1717,7 @@ public class ActivityInfo extends ComponentInfo implements Parcelable {
dest.writeFloat(mMinAspectRatio);
dest.writeBoolean(supportsSizeChanges);
sForStringSet.parcel(mKnownActivityEmbeddingCerts, dest, flags);
- dest.writeString8(targetDisplayCategory);
+ dest.writeString8(requiredDisplayCategory);
}
/**
@@ -1844,7 +1843,7 @@ public class ActivityInfo extends ComponentInfo implements Parcelable {
if (mKnownActivityEmbeddingCerts.isEmpty()) {
mKnownActivityEmbeddingCerts = null;
}
- targetDisplayCategory = source.readString8();
+ requiredDisplayCategory = source.readString8();
}
/**
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index c79f99d9d8c9..1f01ae971d2b 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -867,10 +867,15 @@ public class PackageInstaller {
*/
public void checkInstallConstraints(@NonNull List<String> packageNames,
@NonNull InstallConstraints constraints,
+ @NonNull @CallbackExecutor Executor executor,
@NonNull Consumer<InstallConstraintsResult> callback) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
try {
var remoteCallback = new RemoteCallback(b -> {
- callback.accept(b.getParcelable("result", InstallConstraintsResult.class));
+ executor.execute(() -> {
+ callback.accept(b.getParcelable("result", InstallConstraintsResult.class));
+ });
});
mInstaller.checkInstallConstraints(
mInstallerPackageName, packageNames, constraints, remoteCallback);
@@ -3675,7 +3680,7 @@ public class PackageInstaller {
}
/**
- * The callback result of {@link #checkInstallConstraints(List, InstallConstraints, Consumer)}.
+ * The callback result of {@link #checkInstallConstraints(List, InstallConstraints, Executor, Consumer)}.
*/
@DataClass(genParcelable = true, genHiddenConstructor = true)
public static final class InstallConstraintsResult implements Parcelable {
@@ -3783,7 +3788,7 @@ public class PackageInstaller {
/**
* A class to encapsulate constraints for installation.
*
- * When used with {@link #checkInstallConstraints(List, InstallConstraints, Consumer)}, it
+ * When used with {@link #checkInstallConstraints(List, InstallConstraints, Executor, Consumer)}, it
* specifies the conditions to check against for the packages in question. This can be used
* by app stores to deliver auto updates without disrupting the user experience (referred as
* gentle update) - for example, an app store might hold off updates when it find out the
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 88b5e021882a..ec490d10d45e 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -3849,7 +3849,10 @@ public abstract class PackageManager {
/**
* Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:
- * The device supports running activities on secondary displays.
+ * The device supports running activities on secondary displays. Displays here
+ * refers to both physical and virtual displays. Disabling this feature can impact
+ * support for application projection use-cases and support for virtual devices
+ * on the device.
*/
@SdkConstant(SdkConstantType.FEATURE)
public static final String FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS
diff --git a/core/java/android/content/pm/PermissionInfo.java b/core/java/android/content/pm/PermissionInfo.java
index 61fc6f6775c1..4fa80d7d7f40 100644
--- a/core/java/android/content/pm/PermissionInfo.java
+++ b/core/java/android/content/pm/PermissionInfo.java
@@ -250,6 +250,16 @@ public class PermissionInfo extends PackageItemInfo implements Parcelable {
/**
* Additional flag for {@link #protectionLevel}, corresponding
+ * to the <code>module</code> value of
+ * {@link android.R.attr#protectionLevel}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int PROTECTION_FLAG_MODULE = 0x400000;
+
+ /**
+ * Additional flag for {@link #protectionLevel}, corresponding
* to the <code>companion</code> value of
* {@link android.R.attr#protectionLevel}.
*
@@ -320,6 +330,7 @@ public class PermissionInfo extends PackageItemInfo implements Parcelable {
PROTECTION_FLAG_RECENTS,
PROTECTION_FLAG_ROLE,
PROTECTION_FLAG_KNOWN_SIGNER,
+ PROTECTION_FLAG_MODULE,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ProtectionFlags {}
@@ -593,6 +604,9 @@ public class PermissionInfo extends PackageItemInfo implements Parcelable {
if ((level & PermissionInfo.PROTECTION_FLAG_KNOWN_SIGNER) != 0) {
protLevel.append("|knownSigner");
}
+ if ((level & PermissionInfo.PROTECTION_FLAG_MODULE) != 0) {
+ protLevel.append(("|module"));
+ }
return protLevel.toString();
}
diff --git a/core/java/android/content/pm/ServiceInfo.java b/core/java/android/content/pm/ServiceInfo.java
index 7ea6733fa2ff..fc2c532ee756 100644
--- a/core/java/android/content/pm/ServiceInfo.java
+++ b/core/java/android/content/pm/ServiceInfo.java
@@ -331,8 +331,7 @@ public class ServiceInfo extends ComponentInfo
* Messaging use cases which host local server to relay messages across devices.
*/
@RequiresPermission(
- value = Manifest.permission.FOREGROUND_SERVICE_REMOTE_MESSAGING,
- conditional = true
+ value = Manifest.permission.FOREGROUND_SERVICE_REMOTE_MESSAGING
)
public static final int FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING = 1 << 9;
@@ -360,8 +359,7 @@ public class ServiceInfo extends ComponentInfo
* </p>
*/
@RequiresPermission(
- value = Manifest.permission.FOREGROUND_SERVICE_SYSTEM_EXEMPTED,
- conditional = true
+ value = Manifest.permission.FOREGROUND_SERVICE_SYSTEM_EXEMPTED
)
public static final int FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED = 1 << 10;
@@ -412,6 +410,17 @@ public class ServiceInfo extends ComponentInfo
public static final int FOREGROUND_SERVICE_TYPE_SHORT_SERVICE = 1 << 11;
/**
+ * Constant corresponding to {@code fileManagement} in
+ * the {@link android.R.attr#foregroundServiceType} attribute.
+ * The file management use case which manages files/directories, often involving file I/O
+ * across the file system.
+ */
+ @RequiresPermission(
+ value = Manifest.permission.FOREGROUND_SERVICE_FILE_MANAGEMENT
+ )
+ public static final int FOREGROUND_SERVICE_TYPE_FILE_MANAGEMENT = 1 << 12;
+
+ /**
* Constant corresponding to {@code specialUse} in
* the {@link android.R.attr#foregroundServiceType} attribute.
* Use cases that can't be categorized into any other foreground service types, but also
@@ -457,8 +466,7 @@ public class ServiceInfo extends ComponentInfo
* </pre>
*/
@RequiresPermission(
- value = Manifest.permission.FOREGROUND_SERVICE_SPECIAL_USE,
- conditional = true
+ value = Manifest.permission.FOREGROUND_SERVICE_SPECIAL_USE
)
public static final int FOREGROUND_SERVICE_TYPE_SPECIAL_USE = 1 << 30;
@@ -495,7 +503,8 @@ public class ServiceInfo extends ComponentInfo
FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING,
FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED,
FOREGROUND_SERVICE_TYPE_SHORT_SERVICE,
- FOREGROUND_SERVICE_TYPE_SPECIAL_USE
+ FOREGROUND_SERVICE_TYPE_FILE_MANAGEMENT,
+ FOREGROUND_SERVICE_TYPE_SPECIAL_USE,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ForegroundServiceType {}
@@ -579,6 +588,8 @@ public class ServiceInfo extends ComponentInfo
return "systemExempted";
case FOREGROUND_SERVICE_TYPE_SHORT_SERVICE:
return "shortService";
+ case FOREGROUND_SERVICE_TYPE_FILE_MANAGEMENT:
+ return "fileManagement";
case FOREGROUND_SERVICE_TYPE_SPECIAL_USE:
return "specialUse";
default:
diff --git a/core/java/android/content/pm/TEST_MAPPING b/core/java/android/content/pm/TEST_MAPPING
index 1c1f58a19abc..45e0efb46616 100644
--- a/core/java/android/content/pm/TEST_MAPPING
+++ b/core/java/android/content/pm/TEST_MAPPING
@@ -43,6 +43,36 @@
"name": "ApkVerityTest"
},
{
+ "name": "CtsAppFgsTestCases",
+ "file_patterns": ["(/|^)ServiceInfo[^/]*"],
+ "options": [
+ {
+ "include-annotation": "android.platform.test.annotations.Presubmit"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.LargeTest"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
+ },
+ {
+ "name": "CtsShortFgsTestCases",
+ "file_patterns": ["(/|^)ServiceInfo[^/]*"],
+ "options": [
+ {
+ "include-annotation": "android.platform.test.annotations.Presubmit"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.LargeTest"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
+ },
+ {
"name": "CtsIncrementalInstallHostTestCases",
"options": [
{
diff --git a/core/java/android/credentials/ClearCredentialStateException.java b/core/java/android/credentials/ClearCredentialStateException.java
new file mode 100644
index 000000000000..a6b76a783fc4
--- /dev/null
+++ b/core/java/android/credentials/ClearCredentialStateException.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.CancellationSignal;
+import android.os.OutcomeReceiver;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Represents an error encountered during the
+ * {@link CredentialManager#clearCredentialState(ClearCredentialStateRequest,
+ * CancellationSignal, Executor, OutcomeReceiver)} operation.
+ */
+public class ClearCredentialStateException extends Exception {
+
+ @NonNull
+ public final String errorType;
+
+ /**
+ * Constructs a {@link ClearCredentialStateException}.
+ *
+ * @throws IllegalArgumentException If errorType is empty.
+ */
+ public ClearCredentialStateException(@NonNull String errorType, @Nullable String message) {
+ this(errorType, message, null);
+ }
+
+ /**
+ * Constructs a {@link ClearCredentialStateException}.
+ *
+ * @throws IllegalArgumentException If errorType is empty.
+ */
+ public ClearCredentialStateException(
+ @NonNull String errorType, @Nullable String message, @Nullable Throwable cause) {
+ super(message, cause);
+ this.errorType = Preconditions.checkStringNotEmpty(errorType,
+ "errorType must not be empty");
+ }
+
+ /**
+ * Constructs a {@link ClearCredentialStateException}.
+ *
+ * @throws IllegalArgumentException If errorType is empty.
+ */
+ public ClearCredentialStateException(@NonNull String errorType, @Nullable Throwable cause) {
+ this(errorType, null, cause);
+ }
+
+ /**
+ * Constructs a {@link ClearCredentialStateException}.
+ *
+ * @throws IllegalArgumentException If errorType is empty.
+ */
+ public ClearCredentialStateException(@NonNull String errorType) {
+ this(errorType, null, null);
+ }
+}
diff --git a/core/java/android/credentials/CreateCredentialException.java b/core/java/android/credentials/CreateCredentialException.java
new file mode 100644
index 000000000000..87af20883091
--- /dev/null
+++ b/core/java/android/credentials/CreateCredentialException.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Activity;
+import android.os.CancellationSignal;
+import android.os.OutcomeReceiver;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Represents an error encountered during the
+ * {@link CredentialManager#executeCreateCredential(CreateCredentialRequest,
+ * Activity, CancellationSignal, Executor, OutcomeReceiver)} operation.
+ */
+public class CreateCredentialException extends Exception {
+
+ @NonNull
+ public final String errorType;
+
+ /**
+ * Constructs a {@link CreateCredentialException}.
+ *
+ * @throws IllegalArgumentException If errorType is empty.
+ */
+ public CreateCredentialException(@NonNull String errorType, @Nullable String message) {
+ this(errorType, message, null);
+ }
+
+ /**
+ * Constructs a {@link CreateCredentialException}.
+ *
+ * @throws IllegalArgumentException If errorType is empty.
+ */
+ public CreateCredentialException(
+ @NonNull String errorType, @Nullable String message, @Nullable Throwable cause) {
+ super(message, cause);
+ this.errorType = Preconditions.checkStringNotEmpty(errorType,
+ "errorType must not be empty");
+ }
+
+ /**
+ * Constructs a {@link CreateCredentialException}.
+ *
+ * @throws IllegalArgumentException If errorType is empty.
+ */
+ public CreateCredentialException(@NonNull String errorType, @Nullable Throwable cause) {
+ this(errorType, null, cause);
+ }
+
+ /**
+ * Constructs a {@link CreateCredentialException}.
+ *
+ * @throws IllegalArgumentException If errorType is empty.
+ */
+ public CreateCredentialException(@NonNull String errorType) {
+ this(errorType, null, null);
+ }
+}
diff --git a/core/java/android/credentials/CreateCredentialRequest.java b/core/java/android/credentials/CreateCredentialRequest.java
index 45890392bed7..be887a513e3f 100644
--- a/core/java/android/credentials/CreateCredentialRequest.java
+++ b/core/java/android/credentials/CreateCredentialRequest.java
@@ -52,7 +52,7 @@ public final class CreateCredentialRequest implements Parcelable {
private final Bundle mCandidateQueryData;
/**
- * Determines whether or not the request must only be fulfilled by a system provider.
+ * Determines whether the request must only be fulfilled by a system provider.
*/
private final boolean mRequireSystemProvider;
diff --git a/core/java/android/credentials/CredentialManager.java b/core/java/android/credentials/CredentialManager.java
index 1efac6c2f332..c011949ec6d4 100644
--- a/core/java/android/credentials/CredentialManager.java
+++ b/core/java/android/credentials/CredentialManager.java
@@ -77,7 +77,7 @@ public final class CredentialManager {
@Nullable CancellationSignal cancellationSignal,
@CallbackExecutor @NonNull Executor executor,
@NonNull OutcomeReceiver<
- GetCredentialResponse, CredentialManagerException> callback) {
+ GetCredentialResponse, GetCredentialException> callback) {
requireNonNull(request, "request must not be null");
requireNonNull(activity, "activity must not be null");
requireNonNull(executor, "executor must not be null");
@@ -121,7 +121,7 @@ public final class CredentialManager {
@Nullable CancellationSignal cancellationSignal,
@CallbackExecutor @NonNull Executor executor,
@NonNull OutcomeReceiver<
- CreateCredentialResponse, CredentialManagerException> callback) {
+ CreateCredentialResponse, CreateCredentialException> callback) {
requireNonNull(request, "request must not be null");
requireNonNull(activity, "activity must not be null");
requireNonNull(executor, "executor must not be null");
@@ -167,7 +167,7 @@ public final class CredentialManager {
@NonNull ClearCredentialStateRequest request,
@Nullable CancellationSignal cancellationSignal,
@CallbackExecutor @NonNull Executor executor,
- @NonNull OutcomeReceiver<Void, CredentialManagerException> callback) {
+ @NonNull OutcomeReceiver<Void, ClearCredentialStateException> callback) {
requireNonNull(executor, "executor must not be null");
requireNonNull(callback, "callback must not be null");
@@ -196,10 +196,10 @@ public final class CredentialManager {
private final Activity mActivity;
private final Executor mExecutor;
private final OutcomeReceiver<
- GetCredentialResponse, CredentialManagerException> mCallback;
+ GetCredentialResponse, GetCredentialException> mCallback;
private GetCredentialTransport(Activity activity, Executor executor,
- OutcomeReceiver<GetCredentialResponse, CredentialManagerException> callback) {
+ OutcomeReceiver<GetCredentialResponse, GetCredentialException> callback) {
mActivity = activity;
mExecutor = executor;
mCallback = callback;
@@ -222,9 +222,9 @@ public final class CredentialManager {
}
@Override
- public void onError(int errorCode, String message) {
+ public void onError(String errorType, String message) {
mExecutor.execute(
- () -> mCallback.onError(new CredentialManagerException(errorCode, message)));
+ () -> mCallback.onError(new GetCredentialException(errorType, message)));
}
}
@@ -234,10 +234,10 @@ public final class CredentialManager {
private final Activity mActivity;
private final Executor mExecutor;
private final OutcomeReceiver<
- CreateCredentialResponse, CredentialManagerException> mCallback;
+ CreateCredentialResponse, CreateCredentialException> mCallback;
private CreateCredentialTransport(Activity activity, Executor executor,
- OutcomeReceiver<CreateCredentialResponse, CredentialManagerException> callback) {
+ OutcomeReceiver<CreateCredentialResponse, CreateCredentialException> callback) {
mActivity = activity;
mExecutor = executor;
mCallback = callback;
@@ -260,9 +260,9 @@ public final class CredentialManager {
}
@Override
- public void onError(int errorCode, String message) {
+ public void onError(String errorType, String message) {
mExecutor.execute(
- () -> mCallback.onError(new CredentialManagerException(errorCode, message)));
+ () -> mCallback.onError(new CreateCredentialException(errorType, message)));
}
}
@@ -271,10 +271,10 @@ public final class CredentialManager {
// TODO: listen for cancellation to release callback.
private final Executor mExecutor;
- private final OutcomeReceiver<Void, CredentialManagerException> mCallback;
+ private final OutcomeReceiver<Void, ClearCredentialStateException> mCallback;
private ClearCredentialStateTransport(Executor executor,
- OutcomeReceiver<Void, CredentialManagerException> callback) {
+ OutcomeReceiver<Void, ClearCredentialStateException> callback) {
mExecutor = executor;
mCallback = callback;
}
@@ -285,9 +285,9 @@ public final class CredentialManager {
}
@Override
- public void onError(int errorCode, String message) {
+ public void onError(String errorType, String message) {
mExecutor.execute(
- () -> mCallback.onError(new CredentialManagerException(errorCode, message)));
+ () -> mCallback.onError(new ClearCredentialStateException(errorType, message)));
}
}
}
diff --git a/core/java/android/credentials/CredentialManagerException.java b/core/java/android/credentials/CredentialManagerException.java
deleted file mode 100644
index 8369649fe686..000000000000
--- a/core/java/android/credentials/CredentialManagerException.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.credentials;
-
-import android.annotation.Nullable;
-
-/** Exception class for CredentialManager operations. */
-public class CredentialManagerException extends Exception {
- /** Indicates that an unknown error was encountered. */
- public static final int ERROR_UNKNOWN = 0;
-
- /**
- * The given CredentialManager operation is cancelled by the user.
- *
- * @hide
- */
- public static final int ERROR_USER_CANCELLED = 1;
-
- /**
- * No appropriate provider is found to support the target credential type(s).
- *
- * @hide
- */
- public static final int ERROR_PROVIDER_NOT_FOUND = 2;
-
- public final int errorCode;
-
- public CredentialManagerException(int errorCode, @Nullable String message) {
- super(message);
- this.errorCode = errorCode;
- }
-
- public CredentialManagerException(
- int errorCode, @Nullable String message, @Nullable Throwable cause) {
- super(message, cause);
- this.errorCode = errorCode;
- }
-
- public CredentialManagerException(int errorCode, @Nullable Throwable cause) {
- super(cause);
- this.errorCode = errorCode;
- }
-
- public CredentialManagerException(int errorCode) {
- super();
- this.errorCode = errorCode;
- }
-}
diff --git a/core/java/android/credentials/GetCredentialException.java b/core/java/android/credentials/GetCredentialException.java
new file mode 100644
index 000000000000..4d80237b006a
--- /dev/null
+++ b/core/java/android/credentials/GetCredentialException.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Activity;
+import android.os.CancellationSignal;
+import android.os.OutcomeReceiver;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Represents an error encountered during the
+ * {@link CredentialManager#executeGetCredential(GetCredentialRequest,
+ * Activity, CancellationSignal, Executor, OutcomeReceiver)} operation.
+ */
+public class GetCredentialException extends Exception {
+
+ @NonNull
+ public final String errorType;
+
+ /**
+ * Constructs a {@link GetCredentialException}.
+ *
+ * @throws IllegalArgumentException If errorType is empty.
+ */
+ public GetCredentialException(@NonNull String errorType, @Nullable String message) {
+ this(errorType, message, null);
+ }
+
+ /**
+ * Constructs a {@link GetCredentialException}.
+ *
+ * @throws IllegalArgumentException If errorType is empty.
+ */
+ public GetCredentialException(
+ @NonNull String errorType, @Nullable String message, @Nullable Throwable cause) {
+ super(message, cause);
+ this.errorType = Preconditions.checkStringNotEmpty(errorType,
+ "errorType must not be empty");
+ }
+
+ /**
+ * Constructs a {@link GetCredentialException}.
+ *
+ * @throws IllegalArgumentException If errorType is empty.
+ */
+ public GetCredentialException(@NonNull String errorType, @Nullable Throwable cause) {
+ this(errorType, null, cause);
+ }
+
+ /**
+ * Constructs a {@link GetCredentialException}.
+ *
+ * @throws IllegalArgumentException If errorType is empty.
+ */
+ public GetCredentialException(@NonNull String errorType) {
+ this(errorType, null, null);
+ }
+}
diff --git a/core/java/android/credentials/GetCredentialOption.java b/core/java/android/credentials/GetCredentialOption.java
index ed93daef20d3..47731dd7b517 100644
--- a/core/java/android/credentials/GetCredentialOption.java
+++ b/core/java/android/credentials/GetCredentialOption.java
@@ -38,13 +38,20 @@ public final class GetCredentialOption implements Parcelable {
private final String mType;
/**
- * The request data.
+ * The full request data.
*/
@NonNull
- private final Bundle mData;
+ private final Bundle mCredentialRetrievalData;
/**
- * Determines whether or not the request must only be fulfilled by a system provider.
+ * The partial request data that will be sent to the provider during the initial credential
+ * candidate query stage.
+ */
+ @NonNull
+ private final Bundle mCandidateQueryData;
+
+ /**
+ * Determines whether the request must only be fulfilled by a system provider.
*/
private final boolean mRequireSystemProvider;
@@ -57,11 +64,27 @@ public final class GetCredentialOption implements Parcelable {
}
/**
- * Returns the request data.
+ * Returns the full request data.
*/
@NonNull
- public Bundle getData() {
- return mData;
+ public Bundle getCredentialRetrievalData() {
+ return mCredentialRetrievalData;
+ }
+
+ /**
+ * Returns the partial request data that will be sent to the provider during the initial
+ * credential candidate query stage.
+ *
+ * For security reason, a provider will receive the request data in two stages. First it gets
+ * this partial request that do not contain sensitive user information; it uses this
+ * information to provide credential candidates that the [@code CredentialManager] will show to
+ * the user. Next, the full request data, {@link #getCredentialRetrievalData()}, will be sent to
+ * a provider only if the user further grants the consent by choosing a candidate from the
+ * provider.
+ */
+ @NonNull
+ public Bundle getCandidateQueryData() {
+ return mCandidateQueryData;
}
/**
@@ -75,7 +98,8 @@ public final class GetCredentialOption implements Parcelable {
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeString8(mType);
- dest.writeBundle(mData);
+ dest.writeBundle(mCredentialRetrievalData);
+ dest.writeBundle(mCandidateQueryData);
dest.writeBoolean(mRequireSystemProvider);
}
@@ -88,7 +112,8 @@ public final class GetCredentialOption implements Parcelable {
public String toString() {
return "GetCredentialOption {"
+ "type=" + mType
- + ", data=" + mData
+ + ", requestData=" + mCredentialRetrievalData
+ + ", candidateQueryData=" + mCandidateQueryData
+ ", requireSystemProvider=" + mRequireSystemProvider
+ "}";
}
@@ -96,44 +121,52 @@ public final class GetCredentialOption implements Parcelable {
/**
* Constructs a {@link GetCredentialOption}.
*
- * @param type the requested credential type
- * @param data the request data
- * @param requireSystemProvider whether or not the request must only be fulfilled by a system
- * provider
- *
+ * @param type the requested credential type
+ * @param credentialRetrievalData the request data
+ * @param candidateQueryData the partial request data that will be sent to the provider
+ * during the initial credential candidate query stage
+ * @param requireSystemProvider whether the request must only be fulfilled by a system
+ * provider
* @throws IllegalArgumentException If type is empty.
*/
public GetCredentialOption(
@NonNull String type,
- @NonNull Bundle data,
+ @NonNull Bundle credentialRetrievalData,
+ @NonNull Bundle candidateQueryData,
boolean requireSystemProvider) {
mType = Preconditions.checkStringNotEmpty(type, "type must not be empty");
- mData = requireNonNull(data, "data must not be null");
+ mCredentialRetrievalData = requireNonNull(credentialRetrievalData,
+ "requestData must not be null");
+ mCandidateQueryData = requireNonNull(candidateQueryData,
+ "candidateQueryData must not be null");
mRequireSystemProvider = requireSystemProvider;
}
private GetCredentialOption(@NonNull Parcel in) {
String type = in.readString8();
Bundle data = in.readBundle();
+ Bundle candidateQueryData = in.readBundle();
boolean requireSystemProvider = in.readBoolean();
mType = type;
AnnotationValidations.validate(NonNull.class, null, mType);
- mData = data;
- AnnotationValidations.validate(NonNull.class, null, mData);
+ mCredentialRetrievalData = data;
+ AnnotationValidations.validate(NonNull.class, null, mCredentialRetrievalData);
+ mCandidateQueryData = candidateQueryData;
+ AnnotationValidations.validate(NonNull.class, null, mCandidateQueryData);
mRequireSystemProvider = requireSystemProvider;
}
public static final @NonNull Parcelable.Creator<GetCredentialOption> CREATOR =
new Parcelable.Creator<GetCredentialOption>() {
- @Override
- public GetCredentialOption[] newArray(int size) {
- return new GetCredentialOption[size];
- }
-
- @Override
- public GetCredentialOption createFromParcel(@NonNull Parcel in) {
- return new GetCredentialOption(in);
- }
- };
+ @Override
+ public GetCredentialOption[] newArray(int size) {
+ return new GetCredentialOption[size];
+ }
+
+ @Override
+ public GetCredentialOption createFromParcel(@NonNull Parcel in) {
+ return new GetCredentialOption(in);
+ }
+ };
}
diff --git a/core/java/android/credentials/IClearCredentialStateCallback.aidl b/core/java/android/credentials/IClearCredentialStateCallback.aidl
index f8b7ae440680..e18e08716729 100644
--- a/core/java/android/credentials/IClearCredentialStateCallback.aidl
+++ b/core/java/android/credentials/IClearCredentialStateCallback.aidl
@@ -23,5 +23,5 @@ package android.credentials;
*/
interface IClearCredentialStateCallback {
oneway void onSuccess();
- oneway void onError(int errorCode, String message);
+ oneway void onError(String errorType, String message);
} \ No newline at end of file
diff --git a/core/java/android/credentials/ICreateCredentialCallback.aidl b/core/java/android/credentials/ICreateCredentialCallback.aidl
index 87fd36fe78f4..f6890a94bc19 100644
--- a/core/java/android/credentials/ICreateCredentialCallback.aidl
+++ b/core/java/android/credentials/ICreateCredentialCallback.aidl
@@ -27,5 +27,5 @@ import android.credentials.CreateCredentialResponse;
interface ICreateCredentialCallback {
oneway void onPendingIntent(in PendingIntent pendingIntent);
oneway void onResponse(in CreateCredentialResponse response);
- oneway void onError(int errorCode, String message);
+ oneway void onError(String errorType, String message);
} \ No newline at end of file
diff --git a/core/java/android/credentials/IGetCredentialCallback.aidl b/core/java/android/credentials/IGetCredentialCallback.aidl
index da152bad2da9..6d1182aec7bf 100644
--- a/core/java/android/credentials/IGetCredentialCallback.aidl
+++ b/core/java/android/credentials/IGetCredentialCallback.aidl
@@ -27,5 +27,5 @@ import android.credentials.GetCredentialResponse;
interface IGetCredentialCallback {
oneway void onPendingIntent(in PendingIntent pendingIntent);
oneway void onResponse(in GetCredentialResponse response);
- oneway void onError(int errorCode, String message);
+ oneway void onError(String errorType, String message);
} \ No newline at end of file
diff --git a/core/java/android/hardware/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java
index 18118f5bfe29..161b1b71121e 100644
--- a/core/java/android/hardware/SystemSensorManager.java
+++ b/core/java/android/hardware/SystemSensorManager.java
@@ -135,7 +135,7 @@ public class SystemSensorManager extends SensorManager {
private final boolean mIsPackageDebuggable;
private final Context mContext;
private final long mNativeInstance;
- private final VirtualDeviceManager mVdm;
+ private VirtualDeviceManager mVdm;
private Optional<Boolean> mHasHighSamplingRateSensorsPermission = Optional.empty();
@@ -154,7 +154,6 @@ public class SystemSensorManager extends SensorManager {
mContext = context;
mNativeInstance = nativeCreate(context.getOpPackageName());
mIsPackageDebuggable = (0 != (appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE));
- mVdm = mContext.getSystemService(VirtualDeviceManager.class);
// initialize the sensor list
for (int index = 0;; ++index) {
@@ -170,8 +169,7 @@ public class SystemSensorManager extends SensorManager {
@Override
public List<Sensor> getSensorList(int type) {
final int deviceId = mContext.getDeviceId();
- if (deviceId == DEFAULT_DEVICE_ID || mVdm == null
- || mVdm.getDevicePolicy(deviceId, POLICY_TYPE_SENSORS) == DEVICE_POLICY_DEFAULT) {
+ if (isDeviceSensorPolicyDefault(deviceId)) {
return super.getSensorList(type);
}
@@ -207,8 +205,7 @@ public class SystemSensorManager extends SensorManager {
@Override
protected List<Sensor> getFullSensorList() {
final int deviceId = mContext.getDeviceId();
- if (deviceId == DEFAULT_DEVICE_ID || mVdm == null
- || mVdm.getDevicePolicy(deviceId, POLICY_TYPE_SENSORS) == DEVICE_POLICY_DEFAULT) {
+ if (isDeviceSensorPolicyDefault(deviceId)) {
return mFullSensorsList;
}
@@ -1136,6 +1133,17 @@ public class SystemSensorManager extends SensorManager {
parameter.type, parameter.floatValues, parameter.intValues) == 0;
}
+ private boolean isDeviceSensorPolicyDefault(int deviceId) {
+ if (deviceId == DEFAULT_DEVICE_ID) {
+ return true;
+ }
+ if (mVdm == null) {
+ mVdm = mContext.getSystemService(VirtualDeviceManager.class);
+ }
+ return mVdm == null
+ || mVdm.getDevicePolicy(deviceId, POLICY_TYPE_SENSORS) == DEVICE_POLICY_DEFAULT;
+ }
+
/**
* Checks if a sensor should be capped according to HIGH_SAMPLING_RATE_SENSORS
* permission.
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 1ee2423e62fd..6e72b5f291f0 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -4613,7 +4613,6 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
* <p>This key is available on all devices.</p>
* @see #SENSOR_READOUT_TIMESTAMP_NOT_SUPPORTED
* @see #SENSOR_READOUT_TIMESTAMP_HARDWARE
- * @hide
*/
@PublicKey
@NonNull
@@ -5572,4 +5571,6 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
+
+
}
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index 44f8b1bfda07..b2428b1d80a3 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -1695,7 +1695,6 @@ public abstract class CameraMetadata<TKey> {
* <p>This camera device doesn't support readout timestamp and onReadoutStarted
* callback.</p>
* @see CameraCharacteristics#SENSOR_READOUT_TIMESTAMP
- * @hide
*/
public static final int SENSOR_READOUT_TIMESTAMP_NOT_SUPPORTED = 0;
@@ -1705,7 +1704,6 @@ public abstract class CameraMetadata<TKey> {
* readout timestamp is generated by the camera hardware and it has the same accuracy
* and timing characteristics of the start-of-exposure time.</p>
* @see CameraCharacteristics#SENSOR_READOUT_TIMESTAMP
- * @hide
*/
public static final int SENSOR_READOUT_TIMESTAMP_HARDWARE = 1;
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index ea3e4a807baf..43bfdcc8d898 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -4154,6 +4154,38 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
public static final Key<Integer> DISTORTION_CORRECTION_MODE =
new Key<Integer>("android.distortionCorrection.mode", int.class);
+ /**
+ * <p>Strength of the extension post-processing effect</p>
+ * <p>This control allows Camera extension clients to configure the strength of the applied
+ * extension effect. Strength equal to 0 means that the extension must not apply any
+ * post-processing and return a regular captured frame. Strength equal to 100 is the
+ * default level of post-processing applied when the control is not supported or not set
+ * by the client. Values between 0 and 100 will have different effect depending on the
+ * extension type as described below:</p>
+ * <ul>
+ * <li>{@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_BOKEH BOKEH} -
+ * the strength is expected to control the amount of blur.</li>
+ * <li>{@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_HDR HDR} and
+ * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_NIGHT NIGHT} -
+ * the strength can control the amount of images fused and the brightness of the final image.</li>
+ * <li>{@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_FACE_RETOUCH FACE_RETOUCH} -
+ * the strength value will control the amount of cosmetic enhancement and skin
+ * smoothing.</li>
+ * </ul>
+ * <p>The control will be supported if the capture request key is part of the list generated by
+ * {@link android.hardware.camera2.CameraExtensionCharacteristics#getAvailableCaptureRequestKeys }.
+ * The control is only defined and available to clients sending capture requests via
+ * {@link android.hardware.camera2.CameraExtensionSession }.
+ * The default value is 100.</p>
+ * <p><b>Range of valid values:</b><br>
+ * 0 - 100</p>
+ * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ */
+ @PublicKey
+ @NonNull
+ public static final Key<Integer> EXTENSION_STRENGTH =
+ new Key<Integer>("android.extension.strength", int.class);
+
/*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~
* End generated code
*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~O@*/
@@ -4163,4 +4195,6 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
+
+
}
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index 285c9331fd4a..fb52cc6a0196 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -5576,6 +5576,62 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
public static final Key<Integer> DISTORTION_CORRECTION_MODE =
new Key<Integer>("android.distortionCorrection.mode", int.class);
+ /**
+ * <p>Contains the extension type of the currently active extension</p>
+ * <p>The capture result will only be supported and included by camera extension
+ * {@link android.hardware.camera2.CameraExtensionSession sessions}.
+ * In case the extension session was configured to use
+ * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_AUTOMATIC AUTO},
+ * then the extension type value will indicate the currently active extension like
+ * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_HDR HDR},
+ * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_NIGHT NIGHT} etc.
+ * , and will never return
+ * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_AUTOMATIC AUTO}.
+ * In case the extension session was configured to use an extension different from
+ * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_AUTOMATIC AUTO},
+ * then the result type will always match with the configured extension type.</p>
+ * <p><b>Range of valid values:</b><br>
+ * Extension type value listed in
+ * {@link android.hardware.camera2.CameraExtensionCharacteristics }</p>
+ * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ */
+ @PublicKey
+ @NonNull
+ public static final Key<Integer> EXTENSION_CURRENT_TYPE =
+ new Key<Integer>("android.extension.currentType", int.class);
+
+ /**
+ * <p>Strength of the extension post-processing effect</p>
+ * <p>This control allows Camera extension clients to configure the strength of the applied
+ * extension effect. Strength equal to 0 means that the extension must not apply any
+ * post-processing and return a regular captured frame. Strength equal to 100 is the
+ * default level of post-processing applied when the control is not supported or not set
+ * by the client. Values between 0 and 100 will have different effect depending on the
+ * extension type as described below:</p>
+ * <ul>
+ * <li>{@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_BOKEH BOKEH} -
+ * the strength is expected to control the amount of blur.</li>
+ * <li>{@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_HDR HDR} and
+ * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_NIGHT NIGHT} -
+ * the strength can control the amount of images fused and the brightness of the final image.</li>
+ * <li>{@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_FACE_RETOUCH FACE_RETOUCH} -
+ * the strength value will control the amount of cosmetic enhancement and skin
+ * smoothing.</li>
+ * </ul>
+ * <p>The control will be supported if the capture request key is part of the list generated by
+ * {@link android.hardware.camera2.CameraExtensionCharacteristics#getAvailableCaptureRequestKeys }.
+ * The control is only defined and available to clients sending capture requests via
+ * {@link android.hardware.camera2.CameraExtensionSession }.
+ * The default value is 100.</p>
+ * <p><b>Range of valid values:</b><br>
+ * 0 - 100</p>
+ * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ */
+ @PublicKey
+ @NonNull
+ public static final Key<Integer> EXTENSION_STRENGTH =
+ new Key<Integer>("android.extension.strength", int.class);
+
/*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~
* End generated code
*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~O@*/
@@ -5585,4 +5641,6 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
+
+
}
diff --git a/core/java/android/hardware/display/AmbientDisplayConfiguration.java b/core/java/android/hardware/display/AmbientDisplayConfiguration.java
index 8c71b363eb7b..47541ca16cda 100644
--- a/core/java/android/hardware/display/AmbientDisplayConfiguration.java
+++ b/core/java/android/hardware/display/AmbientDisplayConfiguration.java
@@ -40,6 +40,7 @@ public class AmbientDisplayConfiguration {
private static final String TAG = "AmbientDisplayConfig";
private final Context mContext;
private final boolean mAlwaysOnByDefault;
+ private final boolean mPickupGestureEnabledByDefault;
/** Copied from android.provider.Settings.Secure since these keys are hidden. */
private static final String[] DOZE_SETTINGS = {
@@ -65,6 +66,8 @@ public class AmbientDisplayConfiguration {
public AmbientDisplayConfiguration(Context context) {
mContext = context;
mAlwaysOnByDefault = mContext.getResources().getBoolean(R.bool.config_dozeAlwaysOnEnabled);
+ mPickupGestureEnabledByDefault =
+ mContext.getResources().getBoolean(R.bool.config_dozePickupGestureEnabled);
}
/** @hide */
@@ -95,7 +98,8 @@ public class AmbientDisplayConfiguration {
/** @hide */
public boolean pickupGestureEnabled(int user) {
- return boolSettingDefaultOn(Settings.Secure.DOZE_PICK_UP_GESTURE, user)
+ return boolSetting(Settings.Secure.DOZE_PICK_UP_GESTURE, user,
+ mPickupGestureEnabledByDefault ? 1 : 0)
&& dozePickupSensorAvailable();
}
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index f7675e835eb2..23d108fbe36a 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -419,6 +419,15 @@ public final class DisplayManager {
@TestApi
public static final int VIRTUAL_DISPLAY_FLAG_OWN_FOCUS = 1 << 14;
+ /**
+ * Virtual display flags: Indicates that the display should not be a part of the default
+ * DisplayGroup and instead be part of a DisplayGroup associated with its virtual device.
+ *
+ * @see #createVirtualDisplay
+ * @hide
+ */
+ public static final int VIRTUAL_DISPLAY_FLAG_DEVICE_DISPLAY_GROUP = 1 << 15;
+
/** @hide */
@IntDef(prefix = {"MATCH_CONTENT_FRAMERATE_"}, value = {
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index 1c2c895a1912..829908fc11d6 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -423,8 +423,6 @@ public abstract class DisplayManagerInternal {
public static final int POLICY_DIM = 2;
// Policy: Make the screen bright as usual.
public static final int POLICY_BRIGHT = 3;
- // Policy: Keep the screen and display optimized for VR mode.
- public static final int POLICY_VR = 4;
// The basic overall policy to apply: off, doze, dim or bright.
public int policy;
@@ -489,10 +487,6 @@ public abstract class DisplayManagerInternal {
return policy == POLICY_BRIGHT || policy == POLICY_DIM;
}
- public boolean isVr() {
- return policy == POLICY_VR;
- }
-
public void copyFrom(DisplayPowerRequest other) {
policy = other.policy;
useProximitySensor = other.useProximitySensor;
@@ -566,8 +560,6 @@ public abstract class DisplayManagerInternal {
return "DIM";
case POLICY_BRIGHT:
return "BRIGHT";
- case POLICY_VR:
- return "VR";
default:
return Integer.toString(policy);
}
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index 6f63dbfc3ba9..a748b600a65e 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -1014,8 +1014,22 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
return;
}
- // TODO(b/218388821): Propagate all the parameters to FingerprintService.
- Slog.e(TAG, "onPointerDown: not implemented!");
+ final PointerContext pc = new PointerContext();
+ pc.pointerId = pointerId;
+ pc.x = x;
+ pc.y = y;
+ pc.minor = minor;
+ pc.major = major;
+ pc.orientation = orientation;
+ pc.time = time;
+ pc.gestureStart = gestureStart;
+ pc.isAod = isAod;
+
+ try {
+ mService.onPointerDown(requestId, sensorId, pc);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
/**
@@ -1040,8 +1054,22 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
return;
}
- // TODO(b/218388821): Propagate all the parameters to FingerprintService.
- Slog.e(TAG, "onPointerUp: not implemented!");
+ final PointerContext pc = new PointerContext();
+ pc.pointerId = pointerId;
+ pc.x = x;
+ pc.y = y;
+ pc.minor = minor;
+ pc.major = major;
+ pc.orientation = orientation;
+ pc.time = time;
+ pc.gestureStart = gestureStart;
+ pc.isAod = isAod;
+
+ try {
+ mService.onPointerUp(requestId, sensorId, pc);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
/**
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index bdcbcaacfae4..eef0f421d29f 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -32,6 +32,8 @@ import android.hardware.lights.LightState;
import android.os.IBinder;
import android.os.IVibratorStateListener;
import android.os.VibrationEffect;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodSubtype;
import android.view.InputDevice;
import android.view.InputEvent;
import android.view.InputMonitor;
@@ -107,6 +109,36 @@ interface IInputManager {
void removeKeyboardLayoutForInputDevice(in InputDeviceIdentifier identifier,
String keyboardLayoutDescriptor);
+ // New Keyboard layout config APIs
+ String getKeyboardLayoutForInputDevice(in InputDeviceIdentifier identifier, int userId,
+ in InputMethodInfo imeInfo, in InputMethodSubtype imeSubtype);
+
+ @EnforcePermission("SET_KEYBOARD_LAYOUT")
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+ + "android.Manifest.permission.SET_KEYBOARD_LAYOUT)")
+ void setKeyboardLayoutForInputDevice(in InputDeviceIdentifier identifier, int userId,
+ in InputMethodInfo imeInfo, in InputMethodSubtype imeSubtype,
+ String keyboardLayoutDescriptor);
+
+ String[] getKeyboardLayoutListForInputDevice(in InputDeviceIdentifier identifier, int userId,
+ in InputMethodInfo imeInfo, in InputMethodSubtype imeSubtype);
+
+ // Modifier key remapping APIs.
+ @EnforcePermission("REMAP_MODIFIER_KEYS")
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+ + "android.Manifest.permission.REMAP_MODIFIER_KEYS)")
+ void remapModifierKey(int fromKey, int toKey);
+
+ @EnforcePermission("REMAP_MODIFIER_KEYS")
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+ + "android.Manifest.permission.REMAP_MODIFIER_KEYS)")
+ void clearAllModifierKeyRemappings();
+
+ @EnforcePermission("REMAP_MODIFIER_KEYS")
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+ + "android.Manifest.permission.REMAP_MODIFIER_KEYS)")
+ Map getModifierKeyRemapping();
+
// Registers an input devices changed listener.
void registerInputDevicesChangedListener(IInputDevicesChangedListener listener);
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index a157a8f6c6d7..373541734be0 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -26,6 +26,7 @@ import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SystemService;
import android.annotation.TestApi;
+import android.annotation.UserIdInt;
import android.app.ActivityThread;
import android.compat.annotation.ChangeId;
import android.compat.annotation.UnsupportedAppUsage;
@@ -66,6 +67,8 @@ import android.view.MotionEvent;
import android.view.PointerIcon;
import android.view.VerifiedInputEvent;
import android.view.WindowManager.LayoutParams;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodSubtype;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -75,6 +78,7 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Executor;
@@ -250,6 +254,31 @@ public final class InputManager {
})
public @interface SwitchState {}
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "REMAPPABLE_MODIFIER_KEY_" }, value = {
+ RemappableModifierKey.REMAPPABLE_MODIFIER_KEY_CTRL_LEFT,
+ RemappableModifierKey.REMAPPABLE_MODIFIER_KEY_CTRL_RIGHT,
+ RemappableModifierKey.REMAPPABLE_MODIFIER_KEY_META_LEFT,
+ RemappableModifierKey.REMAPPABLE_MODIFIER_KEY_META_RIGHT,
+ RemappableModifierKey.REMAPPABLE_MODIFIER_KEY_ALT_LEFT,
+ RemappableModifierKey.REMAPPABLE_MODIFIER_KEY_ALT_RIGHT,
+ RemappableModifierKey.REMAPPABLE_MODIFIER_KEY_SHIFT_LEFT,
+ RemappableModifierKey.REMAPPABLE_MODIFIER_KEY_SHIFT_RIGHT,
+ RemappableModifierKey.REMAPPABLE_MODIFIER_KEY_CAPS_LOCK,
+ })
+ public @interface RemappableModifierKey {
+ int REMAPPABLE_MODIFIER_KEY_CTRL_LEFT = KeyEvent.KEYCODE_CTRL_LEFT;
+ int REMAPPABLE_MODIFIER_KEY_CTRL_RIGHT = KeyEvent.KEYCODE_CTRL_RIGHT;
+ int REMAPPABLE_MODIFIER_KEY_META_LEFT = KeyEvent.KEYCODE_META_LEFT;
+ int REMAPPABLE_MODIFIER_KEY_META_RIGHT = KeyEvent.KEYCODE_META_RIGHT;
+ int REMAPPABLE_MODIFIER_KEY_ALT_LEFT = KeyEvent.KEYCODE_ALT_LEFT;
+ int REMAPPABLE_MODIFIER_KEY_ALT_RIGHT = KeyEvent.KEYCODE_ALT_RIGHT;
+ int REMAPPABLE_MODIFIER_KEY_SHIFT_LEFT = KeyEvent.KEYCODE_SHIFT_LEFT;
+ int REMAPPABLE_MODIFIER_KEY_SHIFT_RIGHT = KeyEvent.KEYCODE_SHIFT_RIGHT;
+ int REMAPPABLE_MODIFIER_KEY_CAPS_LOCK = KeyEvent.KEYCODE_CAPS_LOCK;
+ }
+
/**
* Switch State: Unknown.
*
@@ -851,6 +880,60 @@ public final class InputManager {
}
/**
+ * Remaps modifier keys. Remapping a modifier key to itself will clear any previous remappings
+ * for that key.
+ *
+ * @param fromKey The modifier key getting remapped.
+ * @param toKey The modifier key that it is remapped to.
+ *
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(Manifest.permission.REMAP_MODIFIER_KEYS)
+ public void remapModifierKey(@RemappableModifierKey int fromKey,
+ @RemappableModifierKey int toKey) {
+ try {
+ mIm.remapModifierKey(fromKey, toKey);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Clears all existing modifier key remappings
+ *
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(Manifest.permission.REMAP_MODIFIER_KEYS)
+ public void clearAllModifierKeyRemappings() {
+ try {
+ mIm.clearAllModifierKeyRemappings();
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Provides the current modifier key remapping
+ *
+ * @return a {fromKey, toKey} map that contains the existing modifier key remappings..
+ * {@link RemappableModifierKey}
+ *
+ * @hide
+ */
+ @TestApi
+ @NonNull
+ @RequiresPermission(Manifest.permission.REMAP_MODIFIER_KEYS)
+ public Map<Integer, Integer> getModifierKeyRemapping() {
+ try {
+ return mIm.getModifierKeyRemapping();
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Gets the TouchCalibration applied to the specified input device's coordinates.
*
* @param inputDeviceDescriptor The input device descriptor.
@@ -889,6 +972,91 @@ public final class InputManager {
}
/**
+ * Gets the keyboard layout descriptor for the specified input device, userId, imeInfo and
+ * imeSubtype.
+ *
+ * @param identifier Identifier for the input device
+ * @param userId user profile ID
+ * @param imeInfo contains IME information like imeId, etc.
+ * @param imeSubtype contains IME subtype information like input languageTag, layoutType, etc.
+ * @return The keyboard layout descriptor, or null if no keyboard layout has been set.
+ *
+ * @hide
+ */
+ @Nullable
+ public String getKeyboardLayoutForInputDevice(@NonNull InputDeviceIdentifier identifier,
+ @UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
+ @NonNull InputMethodSubtype imeSubtype) {
+ try {
+ return mIm.getKeyboardLayoutForInputDevice(identifier, userId, imeInfo, imeSubtype);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Sets the keyboard layout descriptor for the specified input device, userId, imeInfo and
+ * imeSubtype.
+ *
+ * <p>
+ * This method may have the side-effect of causing the input device in question to be
+ * reconfigured.
+ * </p>
+ *
+ * @param identifier The identifier for the input device.
+ * @param userId user profile ID
+ * @param imeInfo contains IME information like imeId, etc.
+ * @param imeSubtype contains IME subtype information like input languageTag, layoutType, etc.
+ * @param keyboardLayoutDescriptor The keyboard layout descriptor to use, must not be null.
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.SET_KEYBOARD_LAYOUT)
+ public void setKeyboardLayoutForInputDevice(@NonNull InputDeviceIdentifier identifier,
+ @UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
+ @NonNull InputMethodSubtype imeSubtype, @NonNull String keyboardLayoutDescriptor) {
+ if (identifier == null) {
+ throw new IllegalArgumentException("identifier must not be null");
+ }
+ if (keyboardLayoutDescriptor == null) {
+ throw new IllegalArgumentException("keyboardLayoutDescriptor must not be null");
+ }
+
+ try {
+ mIm.setKeyboardLayoutForInputDevice(identifier, userId, imeInfo, imeSubtype,
+ keyboardLayoutDescriptor);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Gets all keyboard layout descriptors that are enabled for the specified input device, userId,
+ * imeInfo and imeSubtype.
+ *
+ * @param identifier The identifier for the input device.
+ * @param userId user profile ID
+ * @param imeInfo contains IME information like imeId, etc.
+ * @param imeSubtype contains IME subtype information like input languageTag, layoutType, etc.
+ * @return The keyboard layout descriptors.
+ *
+ * @hide
+ */
+ public String[] getKeyboardLayoutListForInputDevice(InputDeviceIdentifier identifier,
+ @UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
+ @NonNull InputMethodSubtype imeSubtype) {
+ if (identifier == null) {
+ throw new IllegalArgumentException("inputDeviceDescriptor must not be null");
+ }
+
+ try {
+ return mIm.getKeyboardLayoutListForInputDevice(identifier, userId, imeInfo, imeSubtype);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Gets the mouse pointer speed.
* <p>
* Only returns the permanent mouse pointer speed. Ignores any temporary pointer
diff --git a/core/java/android/hardware/input/VirtualDpad.java b/core/java/android/hardware/input/VirtualDpad.java
index 4d61553ccb52..8133472961a0 100644
--- a/core/java/android/hardware/input/VirtualDpad.java
+++ b/core/java/android/hardware/input/VirtualDpad.java
@@ -32,8 +32,8 @@ import java.util.Set;
/**
* A virtual dpad representing a key input mechanism on a remote device.
*
- * This registers an InputDevice that is interpreted like a physically-connected device and
- * dispatches received events to it.
+ * <p>This registers an InputDevice that is interpreted like a physically-connected device and
+ * dispatches received events to it.</p>
*
* @hide
*/
@@ -44,6 +44,7 @@ public class VirtualDpad extends VirtualInputDevice {
Collections.unmodifiableSet(
new HashSet<>(
Arrays.asList(
+ KeyEvent.KEYCODE_BACK,
KeyEvent.KEYCODE_DPAD_UP,
KeyEvent.KEYCODE_DPAD_DOWN,
KeyEvent.KEYCODE_DPAD_LEFT,
@@ -58,8 +59,15 @@ public class VirtualDpad extends VirtualInputDevice {
/**
* Sends a key event to the system.
*
- * Supported key codes are KEYCODE_DPAD_UP, KEYCODE_DPAD_DOWN, KEYCODE_DPAD_LEFT,
- * KEYCODE_DPAD_RIGHT and KEYCODE_DPAD_CENTER,
+ * <p>Supported key codes are:
+ * <ul>
+ * <li>{@link KeyEvent.KEYCODE_DPAD_UP}</li>
+ * <li>{@link KeyEvent.KEYCODE_DPAD_DOWN}</li>
+ * <li>{@link KeyEvent.KEYCODE_DPAD_LEFT}</li>
+ * <li>{@link KeyEvent.KEYCODE_DPAD_RIGHT}</li>
+ * <li>{@link KeyEvent.KEYCODE_DPAD_CENTER}</li>
+ * <li>{@link KeyEvent.KEYCODE_BACK}</li>
+ * </ul>
*
* @param event the event to send
*/
diff --git a/core/java/android/hardware/input/VirtualDpadConfig.aidl b/core/java/android/hardware/input/VirtualDpadConfig.aidl
new file mode 100644
index 000000000000..fac90f444a1b
--- /dev/null
+++ b/core/java/android/hardware/input/VirtualDpadConfig.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.input;
+
+parcelable VirtualDpadConfig;
diff --git a/core/java/android/hardware/input/VirtualDpadConfig.java b/core/java/android/hardware/input/VirtualDpadConfig.java
new file mode 100644
index 000000000000..d888dc0bffdf
--- /dev/null
+++ b/core/java/android/hardware/input/VirtualDpadConfig.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.input;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Configurations to create virtual Dpad.
+ *
+ * @hide
+ */
+@SystemApi
+public final class VirtualDpadConfig extends VirtualInputDeviceConfig implements Parcelable {
+ @NonNull
+ public static final Creator<VirtualDpadConfig> CREATOR = new Creator<VirtualDpadConfig>() {
+ @Override
+ public VirtualDpadConfig createFromParcel(Parcel in) {
+ return new VirtualDpadConfig(in);
+ }
+
+ @Override
+ public VirtualDpadConfig[] newArray(int size) {
+ return new VirtualDpadConfig[size];
+ }
+ };
+
+ private VirtualDpadConfig(@NonNull VirtualDpadConfig.Builder builder) {
+ super(builder);
+ }
+
+ private VirtualDpadConfig(@NonNull Parcel in) {
+ super(in);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ }
+
+ /**
+ * Builder for creating a {@link VirtualDpadConfig}.
+ */
+ public static final class Builder extends VirtualInputDeviceConfig.Builder<Builder> {
+
+ /**
+ * Builds the {@link VirtualDpadConfig} instance.
+ */
+ @NonNull
+ public VirtualDpadConfig build() {
+ return new VirtualDpadConfig(this);
+ }
+ }
+}
diff --git a/core/java/android/hardware/input/VirtualInputDeviceConfig.java b/core/java/android/hardware/input/VirtualInputDeviceConfig.java
new file mode 100644
index 000000000000..d3dacc90d1b4
--- /dev/null
+++ b/core/java/android/hardware/input/VirtualInputDeviceConfig.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.input;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+
+/**
+ * Common configurations to create virtual input devices.
+ *
+ * @hide
+ */
+@SystemApi
+public abstract class VirtualInputDeviceConfig {
+ /** The vendor id uniquely identifies the company who manufactured the device. */
+ private final int mVendorId;
+ /**
+ * The product id uniquely identifies which product within the address space of a given vendor,
+ * identified by the device's vendor id.
+ */
+ private final int mProductId;
+ /** The associated display ID of the virtual input device. */
+ private final int mAssociatedDisplayId;
+ /** The name of the virtual input device. */
+ @NonNull
+ private final String mInputDeviceName;
+
+ protected VirtualInputDeviceConfig(@NonNull Builder<? extends Builder<?>> builder) {
+ mVendorId = builder.mVendorId;
+ mProductId = builder.mProductId;
+ mAssociatedDisplayId = builder.mAssociatedDisplayId;
+ mInputDeviceName = builder.mInputDeviceName;
+ }
+
+ protected VirtualInputDeviceConfig(@NonNull Parcel in) {
+ mVendorId = in.readInt();
+ mProductId = in.readInt();
+ mAssociatedDisplayId = in.readInt();
+ mInputDeviceName = in.readString8();
+ }
+
+ /**
+ * The vendor id uniquely identifies the company who manufactured the device.
+ */
+ public int getVendorId() {
+ return mVendorId;
+ }
+
+ /**
+ * The product id uniquely identifies which product within the address space of a given vendor,
+ * identified by the device's vendor id.
+ */
+ public int getProductId() {
+ return mProductId;
+ }
+
+ /**
+ * The associated display ID of the virtual input device.
+ */
+ public int getAssociatedDisplayId() {
+ return mAssociatedDisplayId;
+ }
+
+ /**
+ * The name of the virtual input device.
+ */
+ @NonNull
+ public String getInputDeviceName() {
+ return mInputDeviceName;
+ }
+
+ void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mVendorId);
+ dest.writeInt(mProductId);
+ dest.writeInt(mAssociatedDisplayId);
+ dest.writeString8(mInputDeviceName);
+ }
+
+ /**
+ * A builder for {@link VirtualInputDeviceConfig}
+ *
+ * @param <T> The subclass to be built.
+ */
+ @SuppressWarnings({"StaticFinalBuilder", "MissingBuildMethod"})
+ public abstract static class Builder<T extends Builder<T>> {
+
+ private int mVendorId;
+ private int mProductId;
+ private int mAssociatedDisplayId;
+ @NonNull
+ private String mInputDeviceName;
+
+ /** @see VirtualInputDeviceConfig#getVendorId(). */
+ @NonNull
+ public T setVendorId(int vendorId) {
+ mVendorId = vendorId;
+ return self();
+ }
+
+
+ /** @see VirtualInputDeviceConfig#getProductId(). */
+ @NonNull
+ public T setProductId(int productId) {
+ mProductId = productId;
+ return self();
+ }
+
+ /** @see VirtualInputDeviceConfig#getAssociatedDisplayId(). */
+ @NonNull
+ public T setAssociatedDisplayId(int displayId) {
+ mAssociatedDisplayId = displayId;
+ return self();
+ }
+
+ /** @see VirtualInputDeviceConfig#getInputDeviceName(). */
+ @NonNull
+ public T setInputDeviceName(@NonNull String deviceName) {
+ mInputDeviceName = deviceName;
+ return self();
+ }
+
+ /**
+ * Each subclass should return itself to allow the builder to chain properly
+ */
+ T self() {
+ return (T) this;
+ }
+ }
+}
diff --git a/core/java/android/hardware/input/VirtualKeyboardConfig.aidl b/core/java/android/hardware/input/VirtualKeyboardConfig.aidl
new file mode 100644
index 000000000000..8772e239c583
--- /dev/null
+++ b/core/java/android/hardware/input/VirtualKeyboardConfig.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.input;
+
+parcelable VirtualKeyboardConfig;
diff --git a/core/java/android/hardware/input/VirtualKeyboardConfig.java b/core/java/android/hardware/input/VirtualKeyboardConfig.java
new file mode 100644
index 000000000000..94638574c82d
--- /dev/null
+++ b/core/java/android/hardware/input/VirtualKeyboardConfig.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.input;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+
+/**
+ * Configurations to create virtual keyboard.
+ *
+ * @hide
+ */
+@SystemApi
+public final class VirtualKeyboardConfig extends VirtualInputDeviceConfig implements Parcelable {
+
+ @NonNull
+ public static final Creator<VirtualKeyboardConfig> CREATOR =
+ new Creator<VirtualKeyboardConfig>() {
+ @Override
+ public VirtualKeyboardConfig createFromParcel(Parcel in) {
+ return new VirtualKeyboardConfig(in);
+ }
+
+ @Override
+ public VirtualKeyboardConfig[] newArray(int size) {
+ return new VirtualKeyboardConfig[size];
+ }
+ };
+
+ private VirtualKeyboardConfig(@NonNull Builder builder) {
+ super(builder);
+ }
+
+ private VirtualKeyboardConfig(@NonNull Parcel in) {
+ super(in);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ }
+
+ /**
+ * Builder for creating a {@link VirtualKeyboardConfig}.
+ */
+ public static final class Builder extends VirtualInputDeviceConfig.Builder<Builder> {
+ /**
+ * Builds the {@link VirtualKeyboardConfig} instance.
+ */
+ @NonNull
+ public VirtualKeyboardConfig build() {
+ return new VirtualKeyboardConfig(this);
+ }
+ }
+}
diff --git a/core/java/android/hardware/input/VirtualMouseConfig.aidl b/core/java/android/hardware/input/VirtualMouseConfig.aidl
new file mode 100644
index 000000000000..a0d5fb509d4f
--- /dev/null
+++ b/core/java/android/hardware/input/VirtualMouseConfig.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.input;
+
+parcelable VirtualMouseConfig;
diff --git a/core/java/android/hardware/input/VirtualMouseConfig.java b/core/java/android/hardware/input/VirtualMouseConfig.java
new file mode 100644
index 000000000000..7ad5d0489fae
--- /dev/null
+++ b/core/java/android/hardware/input/VirtualMouseConfig.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.input;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Configurations to create virtual mouse.
+ *
+ * @hide
+ */
+@SystemApi
+public final class VirtualMouseConfig extends VirtualInputDeviceConfig implements Parcelable {
+ @NonNull
+ public static final Creator<VirtualMouseConfig> CREATOR = new Creator<VirtualMouseConfig>() {
+ @Override
+ public VirtualMouseConfig createFromParcel(Parcel in) {
+ return new VirtualMouseConfig(in);
+ }
+
+ @Override
+ public VirtualMouseConfig[] newArray(int size) {
+ return new VirtualMouseConfig[size];
+ }
+ };
+
+ private VirtualMouseConfig(@NonNull VirtualMouseConfig.Builder builder) {
+ super(builder);
+ }
+
+ private VirtualMouseConfig(@NonNull Parcel in) {
+ super(in);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ }
+
+ /**
+ * Builder for creating a {@link VirtualMouseConfig}.
+ */
+ public static final class Builder extends VirtualInputDeviceConfig.Builder<Builder> {
+
+ /**
+ * Builds the {@link VirtualMouseConfig} instance.
+ */
+ @NonNull
+ public VirtualMouseConfig build() {
+ return new VirtualMouseConfig(this);
+ }
+ }
+}
diff --git a/core/java/android/hardware/input/VirtualTouchscreenConfig.aidl b/core/java/android/hardware/input/VirtualTouchscreenConfig.aidl
new file mode 100644
index 000000000000..e4b0edb7f0d1
--- /dev/null
+++ b/core/java/android/hardware/input/VirtualTouchscreenConfig.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.input;
+
+parcelable VirtualTouchscreenConfig;
diff --git a/core/java/android/hardware/input/VirtualTouchscreenConfig.java b/core/java/android/hardware/input/VirtualTouchscreenConfig.java
new file mode 100644
index 000000000000..e358619baf71
--- /dev/null
+++ b/core/java/android/hardware/input/VirtualTouchscreenConfig.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.input;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Configurations to create virtual touchscreen.
+ *
+ * @hide
+ */
+@SystemApi
+public final class VirtualTouchscreenConfig extends VirtualInputDeviceConfig implements Parcelable {
+
+ /** The touchscreen width in pixels. */
+ private final int mWidthInPixels;
+ /** The touchscreen height in pixels. */
+ private final int mHeightInPixels;
+
+ private VirtualTouchscreenConfig(@NonNull Builder builder) {
+ super(builder);
+ mWidthInPixels = builder.mWidthInPixels;
+ mHeightInPixels = builder.mHeightInPixels;
+ }
+
+ private VirtualTouchscreenConfig(@NonNull Parcel in) {
+ super(in);
+ mWidthInPixels = in.readInt();
+ mHeightInPixels = in.readInt();
+ }
+
+ /** Returns the touchscreen width in pixels. */
+ public int getWidthInPixels() {
+ return mWidthInPixels;
+ }
+
+ /** Returns the touchscreen height in pixels. */
+ public int getHeightInPixels() {
+ return mHeightInPixels;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeInt(mWidthInPixels);
+ dest.writeInt(mHeightInPixels);
+ }
+
+ @NonNull
+ public static final Creator<VirtualTouchscreenConfig> CREATOR =
+ new Creator<VirtualTouchscreenConfig>() {
+ @Override
+ public VirtualTouchscreenConfig createFromParcel(Parcel in) {
+ return new VirtualTouchscreenConfig(in);
+ }
+
+ @Override
+ public VirtualTouchscreenConfig[] newArray(int size) {
+ return new VirtualTouchscreenConfig[size];
+ }
+ };
+
+ /**
+ * Builder for creating a {@link VirtualTouchscreenConfig}.
+ */
+ public static final class Builder extends VirtualInputDeviceConfig.Builder<Builder> {
+ private int mWidthInPixels;
+ private int mHeightInPixels;
+
+ /**
+ * @see VirtualTouchscreenConfig#getWidthInPixels().
+ */
+ @NonNull
+ public Builder setWidthInPixels(int widthInPixels) {
+ mWidthInPixels = widthInPixels;
+ return this;
+ }
+
+ /**
+ * @see VirtualTouchscreenConfig#getHeightInPixels().
+ */
+ @NonNull
+ public Builder setHeightInPixels(int heightInPixels) {
+ mHeightInPixels = heightInPixels;
+ return this;
+ }
+
+ /**
+ * Builds the {@link VirtualTouchscreenConfig} instance.
+ */
+ @NonNull
+ public VirtualTouchscreenConfig build() {
+ return new VirtualTouchscreenConfig(this);
+ }
+ }
+}
diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java
index b54da6c6eaea..ac23af4396de 100644
--- a/core/java/android/hardware/location/ContextHubManager.java
+++ b/core/java/android/hardware/location/ContextHubManager.java
@@ -24,6 +24,7 @@ import android.annotation.RequiresPermission;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.SystemService;
+import android.annotation.TestApi;
import android.app.ActivityThread;
import android.app.PendingIntent;
import android.content.Context;
@@ -966,6 +967,34 @@ public final class ContextHubManager {
}
/**
+ * Queries for the list of preloaded nanoapp IDs on the system.
+ *
+ * @param hubInfo The Context Hub to query a list of nanoapp IDs from.
+ *
+ * @return The list of 64-bit IDs of the preloaded nanoapps.
+ *
+ * @throws NullPointerException if hubInfo is null
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
+ @NonNull public long[] getPreloadedNanoAppIds(@NonNull ContextHubInfo hubInfo) {
+ Objects.requireNonNull(hubInfo, "hubInfo cannot be null");
+
+ long[] nanoappIds = null;
+ try {
+ nanoappIds = mService.getPreloadedNanoAppIds(hubInfo);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+
+ if (nanoappIds == null) {
+ nanoappIds = new long[0];
+ }
+ return nanoappIds;
+ }
+
+ /**
* Unregister a callback for receive messages from the context hub.
*
* @see Callback
diff --git a/core/java/android/hardware/location/IContextHubService.aidl b/core/java/android/hardware/location/IContextHubService.aidl
index ced75c4d0247..490267f36b7d 100644
--- a/core/java/android/hardware/location/IContextHubService.aidl
+++ b/core/java/android/hardware/location/IContextHubService.aidl
@@ -109,4 +109,8 @@ interface IContextHubService {
// Queries for a list of nanoapps
@EnforcePermission("ACCESS_CONTEXT_HUB")
void queryNanoApps(int contextHubId, in IContextHubTransactionCallback transactionCallback);
+
+ // Queries for a list of preloaded nanoapps
+ @EnforcePermission("ACCESS_CONTEXT_HUB")
+ long[] getPreloadedNanoAppIds(in ContextHubInfo hubInfo);
}
diff --git a/core/java/android/nfc/AvailableNfcAntenna.java b/core/java/android/nfc/AvailableNfcAntenna.java
index 946ba67b2397..6e6512a04971 100644
--- a/core/java/android/nfc/AvailableNfcAntenna.java
+++ b/core/java/android/nfc/AvailableNfcAntenna.java
@@ -27,13 +27,15 @@ import android.os.Parcelable;
*/
public final class AvailableNfcAntenna implements Parcelable {
/**
- * Location on the antenna on the Y axis in millimeters.
- * 0 is the bottom-left when the user is facing the screen.
+ * Location of the antenna on the Y axis in millimeters.
+ * 0 is the bottom-left when the user is facing the screen
+ * and the device orientation is Portrait.
*/
private final int mLocationX;
/**
- * Location on the antenna on the Y axis in millimeters.
- * 0 is the bottom-left when the user is facing the screen.
+ * Location of the antenna on the Y axis in millimeters.
+ * 0 is the bottom-left when the user is facing the screen
+ * and the device orientation is Portrait.
*/
private final int mLocationY;
@@ -43,16 +45,18 @@ public final class AvailableNfcAntenna implements Parcelable {
}
/**
- * Location on the antenna on the X axis in millimeters.
- * 0 is the bottom-left when the user is facing the screen.
+ * Location of the antenna on the X axis in millimeters.
+ * 0 is the bottom-left when the user is facing the screen
+ * and the device orientation is Portrait.
*/
public int getLocationX() {
return mLocationX;
}
/**
- * Location on the antenna on the Y axis in millimeters.
- * 0 is the bottom-left when the user is facing the screen.
+ * Location of the antenna on the Y axis in millimeters.
+ * 0 is the bottom-left when the user is facing the screen
+ * and the device orientation is Portrait.
*/
public int getLocationY() {
return mLocationY;
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index 8278e89d80df..def0cbd749d4 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -355,6 +355,26 @@ public class Binder implements IBinder {
}
/**
+ * Return the Linux UID assigned to the process that sent the transaction
+ * currently being processed.
+ *
+ * Logs WTF if the current thread is not currently
+ * executing an incoming transaction and the calling identity has not been
+ * explicitly set with {@link #clearCallingIdentity()}
+ *
+ * @hide
+ */
+ public static final int getCallingUidOrWtf() {
+ if (!isDirectlyHandlingTransaction() && !hasExplicitIdentity()) {
+ Log.wtfStack(TAG,
+ "Thread is not in a binder transaction, "
+ + "and the calling identity has not been "
+ + "explicitly set with clearCallingIdentity");
+ }
+ return getCallingUid();
+ }
+
+ /**
* Return the UserHandle assigned to the process that sent you the
* current transaction that is being processed. This is the user
* of the caller. It is distinct from {@link #getCallingUid()} in that a
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 44a1fa51e9bf..249f48608d40 100755
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -1446,28 +1446,6 @@ public class Build {
return IS_DEBUGGABLE;
}
-
- /**
- * Returns true if the device is running a secure build, such as "user" or "userdebug".
- *
- * Secure builds drop adbd privileges by default, though debuggable builds still allow users
- * to gain root access via local shell. See should_drop_privileges() in adb for details.
- * @hide
- */
- private static final boolean IS_SECURE =
- SystemProperties.getBoolean("ro.secure", true);
- /**
- * Returns true if the device is running a secure build, such as "user" or "userdebug".
- *
- * Secure builds drop adbd privileges by default, though debuggable builds still allow users
- * to gain root access via local shell. See should_drop_privileges() in adb for details.
- * @hide
- */
- @TestApi
- public static boolean isSecure() {
- return IS_SECURE;
- }
-
/** {@hide} */
public static final boolean IS_ENG = "eng".equals(TYPE);
/** {@hide} */
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index d31540a65f2f..c2ddf4535d91 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -79,6 +79,7 @@ interface IUserManager {
String getUserAccount(int userId);
void setUserAccount(int userId, String accountName);
long getUserCreationTime(int userId);
+ int getUserSwitchability(int userId);
boolean isUserSwitcherEnabled(boolean showEvenIfNotActionable, int mUserId);
boolean isRestricted(int userId);
boolean canHaveRestrictedProfile(int userId);
diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS
index 5c5af2a26a53..e9a32544e58e 100644
--- a/core/java/android/os/OWNERS
+++ b/core/java/android/os/OWNERS
@@ -71,6 +71,9 @@ per-file Vintf* = file:/platform/system/libvintf:/OWNERS
# Tracing
per-file Trace.java = file:/TRACE_OWNERS
+# PatternMatcher
+per-file PatternMatcher* = file:/PACKAGE_MANAGER_OWNERS
+
# PermissionEnforcer
per-file PermissionEnforcer.java = tweek@google.com, brufino@google.com
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index dd02e022c639..5f2f710e3c01 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -59,7 +59,6 @@ import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.location.LocationManager;
import android.provider.Settings;
-import android.telephony.TelephonyManager;
import android.util.AndroidException;
import android.util.ArraySet;
import android.util.Log;
@@ -1748,7 +1747,7 @@ public class UserManager {
public static final int SWITCHABILITY_STATUS_SYSTEM_USER_LOCKED = 1 << 2;
/**
- * Result returned in {@link #getUserSwitchability()} indicating user swichability.
+ * Result returned in {@link #getUserSwitchability()} indicating user switchability.
* @hide
*/
@Retention(RetentionPolicy.SOURCE)
@@ -2128,25 +2127,16 @@ public class UserManager {
* @hide
*/
@Deprecated
- @RequiresPermission(allOf = {
- Manifest.permission.READ_PHONE_STATE,
- Manifest.permission.MANAGE_USERS}, // Can be INTERACT_ACROSS_USERS instead.
- conditional = true)
+ @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.INTERACT_ACROSS_USERS})
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@UserHandleAware
public boolean canSwitchUsers() {
- boolean allowUserSwitchingWhenSystemUserLocked = Settings.Global.getInt(
- mContext.getContentResolver(),
- Settings.Global.ALLOW_USER_SWITCHING_WHEN_SYSTEM_USER_LOCKED, 0) != 0;
- boolean isSystemUserUnlocked = isUserUnlocked(UserHandle.SYSTEM);
- boolean inCall = false;
- TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class);
- if (telephonyManager != null) {
- inCall = telephonyManager.getCallState() != TelephonyManager.CALL_STATE_IDLE;
+ try {
+ return mService.getUserSwitchability(mUserId) == SWITCHABILITY_STATUS_OK;
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
}
- boolean isUserSwitchDisallowed = hasUserRestrictionForUser(DISALLOW_USER_SWITCH, mUserId);
- return (allowUserSwitchingWhenSystemUserLocked || isSystemUserUnlocked) && !inCall
- && !isUserSwitchDisallowed;
}
/**
@@ -2161,9 +2151,8 @@ public class UserManager {
* @hide
*/
@SystemApi
- @RequiresPermission(allOf = {Manifest.permission.READ_PHONE_STATE,
- android.Manifest.permission.MANAGE_USERS,
- android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
+ @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.INTERACT_ACROSS_USERS})
@UserHandleAware(enabledSinceTargetSdkVersion = Build.VERSION_CODES.TIRAMISU)
public @UserSwitchabilityResult int getUserSwitchability() {
return getUserSwitchability(UserHandle.of(getContextUserIfAppropriate()));
@@ -2180,34 +2169,14 @@ public class UserManager {
* @return A {@link UserSwitchabilityResult} flag indicating if the user is switchable.
* @hide
*/
- @RequiresPermission(allOf = {Manifest.permission.READ_PHONE_STATE,
- android.Manifest.permission.MANAGE_USERS,
- android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
+ @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.INTERACT_ACROSS_USERS})
public @UserSwitchabilityResult int getUserSwitchability(UserHandle userHandle) {
- final TelephonyManager tm =
- (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
-
- int flags = SWITCHABILITY_STATUS_OK;
- if (tm.getCallState() != TelephonyManager.CALL_STATE_IDLE) {
- flags |= SWITCHABILITY_STATUS_USER_IN_CALL;
- }
- if (hasUserRestrictionForUser(DISALLOW_USER_SWITCH, userHandle)) {
- flags |= SWITCHABILITY_STATUS_USER_SWITCH_DISALLOWED;
- }
-
- // System User is always unlocked in Headless System User Mode, so ignore this flag
- if (!isHeadlessSystemUserMode()) {
- final boolean allowUserSwitchingWhenSystemUserLocked = Settings.Global.getInt(
- mContext.getContentResolver(),
- Settings.Global.ALLOW_USER_SWITCHING_WHEN_SYSTEM_USER_LOCKED, 0) != 0;
- final boolean systemUserUnlocked = isUserUnlocked(UserHandle.SYSTEM);
-
- if (!allowUserSwitchingWhenSystemUserLocked && !systemUserUnlocked) {
- flags |= SWITCHABILITY_STATUS_SYSTEM_USER_LOCKED;
- }
+ try {
+ return mService.getUserSwitchability(userHandle.getIdentifier());
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
}
-
- return flags;
}
/**
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
deleted file mode 100644
index ca88ae306c59..000000000000
--- a/core/java/android/provider/DeviceConfig.java
+++ /dev/null
@@ -1,1568 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.provider;
-
-import static android.Manifest.permission.READ_DEVICE_CONFIG;
-import static android.Manifest.permission.WRITE_DEVICE_CONFIG;
-
-import android.annotation.CallbackExecutor;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.RequiresPermission;
-import android.annotation.SystemApi;
-import android.annotation.TestApi;
-import android.content.pm.PackageManager;
-import android.database.ContentObserver;
-import android.net.Uri;
-import android.provider.Settings.Config.SyncDisabledMode;
-import android.provider.Settings.ResetMode;
-import android.util.ArrayMap;
-import android.util.Log;
-import android.util.Pair;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.util.Preconditions;
-
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.Executor;
-
-/**
- * Device level configuration parameters which can be tuned by a separate configuration service.
- * Namespaces that end in "_native" such as {@link #NAMESPACE_NETD_NATIVE} are intended to be used
- * by native code and should be pushed to system properties to make them accessible.
- *
- * @hide
- */
-@SystemApi
-public final class DeviceConfig {
- /**
- * The content:// style URL for the config table.
- *
- * @hide
- */
- public static final Uri CONTENT_URI = Uri.parse("content://" + Settings.AUTHORITY + "/config");
-
- /**
- * Namespace for activity manager related features. These features will be applied
- * immediately upon change.
- *
- * @hide
- */
- @SystemApi
- public static final String NAMESPACE_ACTIVITY_MANAGER = "activity_manager";
-
- /**
- * Namespace for activity manager, specific to the "component alias" feature. We needed a
- * different namespace in order to avoid phonetype from resetting it.
- * @hide
- */
- public static final String NAMESPACE_ACTIVITY_MANAGER_COMPONENT_ALIAS = "activity_manager_ca";
-
- /**
- * Namespace for all activity manager related features that are used at the native level.
- * These features are applied at reboot.
- *
- * @hide
- */
- @SystemApi
- public static final String NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT =
- "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.
- *
- * @hide
- */
- @SystemApi
- public static final String NAMESPACE_APP_COMPAT = "app_compat";
-
- /**
- * Namespace for all app hibernation related features.
- * @hide
- */
- @SystemApi
- public static final String NAMESPACE_APP_HIBERNATION = "app_hibernation";
-
- /**
- * Namespace for all AppSearch related features.
- * @hide
- */
- @SystemApi
- public static final String NAMESPACE_APPSEARCH = "appsearch";
-
- /**
- * Namespace for app standby configurations.
- *
- * @hide
- */
- @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
- public static final String NAMESPACE_APP_STANDBY = "app_standby";
-
- /**
- * Namespace for all App Cloning related features.
- * @hide
- */
- @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
- public static final String NAMESPACE_APP_CLONING = "app_cloning";
-
- /**
- * Namespace for AttentionManagerService related features.
- *
- * @hide
- */
- @SystemApi
- public static final String NAMESPACE_ATTENTION_MANAGER_SERVICE = "attention_manager_service";
-
- /**
- * Namespace for autofill feature that provides suggestions across all apps when
- * the user interacts with input fields.
- *
- * @hide
- */
- @SystemApi
- public static final String NAMESPACE_AUTOFILL = "autofill";
-
- /**
- * Namespace for battery saver feature.
- *
- * @hide
- */
- @SystemApi
- public static final String NAMESPACE_BATTERY_SAVER = "battery_saver";
-
- /**
- * Namespace for blobstore feature that allows apps to share data blobs.
- *
- * @hide
- */
- @SystemApi
- public static final String NAMESPACE_BLOBSTORE = "blobstore";
-
- /**
- * Namespace for all Bluetooth related features.
- *
- * @hide
- */
- @SystemApi
- public static final String NAMESPACE_BLUETOOTH = "bluetooth";
-
- /**
- * Namespace for features relating to clipboard.
- *
- * @hide
- */
- @SystemApi
- public static final String NAMESPACE_CLIPBOARD = "clipboard";
-
- /**
- * Namespace for all networking connectivity related features.
- *
- * @hide
- */
- @SystemApi
- public static final String NAMESPACE_CONNECTIVITY = "connectivity";
-
- /**
- * Namespace for CaptivePortalLogin module.
- *
- * @hide
- */
- @SystemApi
- public static final String NAMESPACE_CAPTIVEPORTALLOGIN = "captive_portal_login";
-
- /**
- * Namespace for Tethering module.
- *
- * @hide
- */
- @SystemApi
- public static final String NAMESPACE_TETHERING = "tethering";
-
-
- /**
- * Namespace for Nearby module.
- *
- * @hide
- */
- @SystemApi
- public static final String NAMESPACE_NEARBY = "nearby";
-
- /**
- * Namespace for content capture feature used by on-device machine intelligence
- * to provide suggestions in a privacy-safe manner.
- *
- * @hide
- */
- @SystemApi
- public static final String NAMESPACE_CONTENT_CAPTURE = "content_capture";
-
- /**
- * Namespace for device idle configurations.
- *
- * @hide
- */
- @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
- @TestApi
- public static final String NAMESPACE_DEVICE_IDLE = "device_idle";
-
- /**
- * Namespace for how dex runs. The feature requires a reboot to reach a clean state.
- *
- * @deprecated No longer used
- * @hide
- */
- @Deprecated
- @SystemApi
- public static final String NAMESPACE_DEX_BOOT = "dex_boot";
-
- /**
- * Namespace for display manager related features. The names to access the properties in this
- * namespace should be defined in {@link android.hardware.display.DisplayManager}.
- *
- * @hide
- */
- @SystemApi
- public static final String NAMESPACE_DISPLAY_MANAGER = "display_manager";
-
- /**
- * Namespace for all Game Driver features.
- *
- * @hide
- */
- @SystemApi
- public static final String NAMESPACE_GAME_DRIVER = "game_driver";
-
- /**
- * Namespace for all HDMI Control features.
- *
- * @hide
- */
- @SystemApi
- public static final String NAMESPACE_HDMI_CONTROL = "hdmi_control";
-
- /**
- * Namespace for all input-related features that are used at the native level.
- * These features are applied at reboot.
- *
- * @hide
- */
- @SystemApi
- public static final String NAMESPACE_INPUT_NATIVE_BOOT = "input_native_boot";
-
- /**
- * Namespace for attention-based features provided by on-device machine intelligence.
- *
- * @hide
- */
- @SystemApi
- public static final String NAMESPACE_INTELLIGENCE_ATTENTION = "intelligence_attention";
-
- /**
- * Definitions for properties related to Content Suggestions.
- *
- * @hide
- */
- public static final String NAMESPACE_INTELLIGENCE_CONTENT_SUGGESTIONS =
- "intelligence_content_suggestions";
-
- /**
- * Namespace for JobScheduler configurations.
- * @hide
- */
- @TestApi
- public static final String NAMESPACE_JOB_SCHEDULER = "jobscheduler";
-
- /**
- * Namespace for all lmkd related features.
- *
- * @hide
- */
- @SystemApi
- public static final String NAMESPACE_LMKD_NATIVE = "lmkd_native";
-
- /**
- * Namespace for all location related features.
- *
- * @hide
- */
- @SystemApi
- public static final String NAMESPACE_LOCATION = "location";
-
- /**
- * Namespace for all media related features.
- *
- * @hide
- */
- @SystemApi
- public static final String NAMESPACE_MEDIA = "media";
-
- /**
- * Namespace for all media native related features.
- *
- * @hide
- */
- @SystemApi
- public static final String NAMESPACE_MEDIA_NATIVE = "media_native";
-
- /**
- * Namespace for all Kernel Multi-Gen LRU feature.
- *
- * @hide
- */
- public static final String NAMESPACE_MGLRU_NATIVE = "mglru_native";
-
- /**
- * Namespace for all netd related features.
- *
- * @hide
- */
- @SystemApi
- public static final String NAMESPACE_NETD_NATIVE = "netd_native";
-
- /**
- * Namespace for all Android NNAPI related features.
- *
- * @hide
- */
- @SystemApi
- public static final String NAMESPACE_NNAPI_NATIVE = "nnapi_native";
-
- /**
- * Namespace for all OnDevicePersonalization related feature.
- * @hide
- */
- @SystemApi
- public static final String NAMESPACE_ON_DEVICE_PERSONALIZATION = "on_device_personalization";
-
- /**
- * Namespace for features related to the Package Manager Service.
- *
- * @hide
- */
- @SystemApi
- public static final String NAMESPACE_PACKAGE_MANAGER_SERVICE = "package_manager_service";
-
- /**
- * Namespace for features related to the Profcollect native Service.
- * These features are applied at reboot.
- *
- * @hide
- */
- @SystemApi
- public static final String NAMESPACE_PROFCOLLECT_NATIVE_BOOT = "profcollect_native_boot";
-
- /**
- * Namespace for features related to Reboot Readiness detection.
- *
- * @hide
- */
- @SystemApi
- public static final String NAMESPACE_REBOOT_READINESS = "reboot_readiness";
-
- /**
- * Namespace for Remote Key Provisioning related features.
- *
- * @hide
- */
- public static final String NAMESPACE_REMOTE_KEY_PROVISIONING_NATIVE =
- "remote_key_provisioning_native";
-
- /**
- * Namespace for Rollback flags that are applied immediately.
- *
- * @hide
- */
- @SystemApi
- public static final String NAMESPACE_ROLLBACK = "rollback";
-
- /**
- * Namespace for Rollback flags that are applied after a reboot.
- *
- * @hide
- */
- @SystemApi
- public static final String NAMESPACE_ROLLBACK_BOOT = "rollback_boot";
-
- /**
- * Namespace for Rotation Resolver Manager Service.
- *
- * @hide
- */
- public static final String NAMESPACE_ROTATION_RESOLVER = "rotation_resolver";
-
- /**
- * Namespace for all runtime related features that don't require a reboot to become active.
- * There are no feature flags using NAMESPACE_RUNTIME.
- *
- * @hide
- */
- @SystemApi
- public static final String NAMESPACE_RUNTIME = "runtime";
-
- /**
- * Namespace for all runtime related features that require system properties for accessing
- * the feature flags from C++ or Java language code. One example is the app image startup
- * cache feature use_app_image_startup_cache.
- *
- * @hide
- */
- @SystemApi
- public static final String NAMESPACE_RUNTIME_NATIVE = "runtime_native";
-
- /**
- * Namespace for all runtime native boot related features. Boot in this case refers to the
- * fact that the properties only take affect after rebooting the device.
- *
- * @hide
- */
- @SystemApi
- public static final String NAMESPACE_RUNTIME_NATIVE_BOOT = "runtime_native_boot";
-
- /**
- * Namespace for system scheduler related features. These features will be applied
- * immediately upon change.
- *
- * @hide
- */
- @SystemApi
- public static final String NAMESPACE_SCHEDULER = "scheduler";
-
- /**
- * Namespace for all SdkSandbox related features.
- * @hide
- */
- @SystemApi
- public static final String NAMESPACE_SDK_SANDBOX = "sdk_sandbox";
-
- /**
- * Namespace for settings statistics features.
- *
- * @hide
- */
- public static final String NAMESPACE_SETTINGS_STATS = "settings_stats";
-
- /**
- * Namespace for all statsd java features that can be applied immediately.
- *
- * @hide
- */
- @SystemApi
- public static final String NAMESPACE_STATSD_JAVA = "statsd_java";
-
- /**
- * Namespace for all statsd java features that are applied on boot.
- *
- * @hide
- */
- @SystemApi
- public static final String NAMESPACE_STATSD_JAVA_BOOT = "statsd_java_boot";
-
- /**
- * Namespace for all statsd native features that can be applied immediately.
- *
- * @hide
- */
- @SystemApi
- public static final String NAMESPACE_STATSD_NATIVE = "statsd_native";
-
- /**
- * Namespace for all statsd native features that are applied on boot.
- *
- * @hide
- */
- @SystemApi
- public static final String NAMESPACE_STATSD_NATIVE_BOOT = "statsd_native_boot";
-
- /**
- * Namespace for storage-related features.
- *
- * @deprecated Replace storage namespace with storage_native_boot.
- * @hide
- */
- @Deprecated
- @SystemApi
- public static final String NAMESPACE_STORAGE = "storage";
-
- /**
- * Namespace for storage-related features, including native and boot.
- *
- * @hide
- */
- @SystemApi
- public static final String NAMESPACE_STORAGE_NATIVE_BOOT = "storage_native_boot";
-
- /**
- * Namespace for all AdServices related features.
- * @hide
- */
- @SystemApi
- public static final String NAMESPACE_ADSERVICES = "adservices";
-
- /**
- * Namespace for all SurfaceFlinger features that are used at the native level.
- * These features are applied on boot or after reboot.
- *
- * @hide
- */
- @SystemApi
- public static final String NAMESPACE_SURFACE_FLINGER_NATIVE_BOOT =
- "surface_flinger_native_boot";
-
- /**
- * Namespace for swcodec native related features.
- *
- * @hide
- */
- @SystemApi
- public static final String NAMESPACE_SWCODEC_NATIVE = "swcodec_native";
-
-
- /**
- * Namespace for System UI related features.
- *
- * @hide
- */
- @SystemApi
- public static final String NAMESPACE_SYSTEMUI = "systemui";
-
- /**
- * Namespace for system time and time zone detection related features / behavior.
- *
- * @hide
- */
- @SystemApi
- public static final String NAMESPACE_SYSTEM_TIME = "system_time";
-
- /**
- * Namespace for TARE configurations.
- *
- * @hide
- */
- public static final String NAMESPACE_TARE = "tare";
-
- /**
- * Telephony related properties.
- *
- * @hide
- */
- @SystemApi
- public static final String NAMESPACE_TELEPHONY = "telephony";
-
- /**
- * Namespace for TextClassifier related features.
- *
- * @hide
- * @see android.provider.Settings.Global.TEXT_CLASSIFIER_CONSTANTS
- */
- @SystemApi
- public static final String NAMESPACE_TEXTCLASSIFIER = "textclassifier";
-
- /**
- * Namespace for contacts provider related features.
- *
- * @hide
- */
- public static final String NAMESPACE_CONTACTS_PROVIDER = "contacts_provider";
-
- /**
- * Namespace for settings ui related features
- *
- * @hide
- */
- public static final String NAMESPACE_SETTINGS_UI = "settings_ui";
-
- /**
- * Namespace for android related features, i.e. for flags that affect not just a single
- * component, but the entire system.
- *
- * The keys for this namespace are defined in {@link AndroidDeviceConfig}.
- *
- * @hide
- */
- @TestApi
- public static final String NAMESPACE_ANDROID = "android";
-
- /**
- * Namespace for window manager related features.
- *
- * @hide
- */
- public static final String NAMESPACE_WINDOW_MANAGER = "window_manager";
-
- /**
- * Namespace for window manager features accessible by native code and
- * loaded once per boot.
- *
- * @hide
- */
- @SystemApi
- public static final String NAMESPACE_WINDOW_MANAGER_NATIVE_BOOT = "window_manager_native_boot";
-
- /**
- * Definitions for selection toolbar related functions.
- *
- * @hide
- */
- @TestApi
- public static final String NAMESPACE_SELECTION_TOOLBAR = "selection_toolbar";
-
- /**
- * Definitions for voice interaction related functions.
- *
- * @hide
- */
- public static final String NAMESPACE_VOICE_INTERACTION = "voice_interaction";
-
- /**
- * Namespace for DevicePolicyManager related features.
- *
- * @hide
- */
- public static final String NAMESPACE_DEVICE_POLICY_MANAGER =
- "device_policy_manager";
-
- /**
- * List of namespaces which can be read without READ_DEVICE_CONFIG permission
- *
- * @hide
- */
- @NonNull
- private static final List<String> PUBLIC_NAMESPACES =
- Arrays.asList(NAMESPACE_TEXTCLASSIFIER, NAMESPACE_RUNTIME, NAMESPACE_STATSD_JAVA,
- NAMESPACE_STATSD_JAVA_BOOT, NAMESPACE_SELECTION_TOOLBAR, NAMESPACE_AUTOFILL,
- NAMESPACE_DEVICE_POLICY_MANAGER, NAMESPACE_CONTENT_CAPTURE);
- /**
- * Privacy related properties definitions.
- *
- * @hide
- */
- @SystemApi
- public static final String NAMESPACE_PRIVACY = "privacy";
-
- /**
- * Namespace for biometrics related features
- *
- * @hide
- */
- @SystemApi
- public static final String NAMESPACE_BIOMETRICS = "biometrics";
-
- /**
- * Permission related properties definitions.
- *
- * @hide
- */
- @SystemApi
- public static final String NAMESPACE_PERMISSIONS = "permissions";
-
- /**
- * Namespace for ota related features.
- *
- * @hide
- */
- @SystemApi
- public static final String NAMESPACE_OTA = "ota";
-
- /**
- * Namespace for all widget related features.
- *
- * @hide
- */
- public static final String NAMESPACE_WIDGET = "widget";
-
- /**
- * Namespace for connectivity thermal power manager features.
- *
- * @hide
- */
- public static final String NAMESPACE_CONNECTIVITY_THERMAL_POWER_MANAGER =
- "connectivity_thermal_power_manager";
-
- /**
- * Namespace for configuration related features.
- *
- * @hide
- */
- public static final String NAMESPACE_CONFIGURATION = "configuration";
-
- /**
- * LatencyTracker properties definitions.
- *
- * @hide
- */
- public static final String NAMESPACE_LATENCY_TRACKER = "latency_tracker";
-
- /**
- * InteractionJankMonitor properties definitions.
- *
- * @hide
- */
- public static final String NAMESPACE_INTERACTION_JANK_MONITOR = "interaction_jank_monitor";
-
- /**
- * Namespace for game overlay related features.
- *
- * @hide
- */
- public static final String NAMESPACE_GAME_OVERLAY = "game_overlay";
-
- /**
- * Namespace for Android Virtualization Framework related features accessible by native code.
- *
- * @hide
- */
- public static final String NAMESPACE_VIRTUALIZATION_FRAMEWORK_NATIVE =
- "virtualization_framework_native";
-
- /**
- * Namespace for Constrain Display APIs related features.
- *
- * @hide
- */
- @TestApi
- public static final String NAMESPACE_CONSTRAIN_DISPLAY_APIS = "constrain_display_apis";
-
- /**
- * Namespace for App Compat Overrides related features.
- *
- * @hide
- */
- @TestApi
- public static final String NAMESPACE_APP_COMPAT_OVERRIDES = "app_compat_overrides";
-
- /**
- * Namespace for all ultra wideband (uwb) related features.
- *
- * @hide
- */
- @SystemApi
- public static final String NAMESPACE_UWB = "uwb";
-
- /**
- * Namespace for AmbientContextEventManagerService related features.
- *
- * @hide
- */
- @SystemApi
- public static final String NAMESPACE_AMBIENT_CONTEXT_MANAGER_SERVICE =
- "ambient_context_manager_service";
-
- /**
- * Namespace for WearableSensingManagerService related features.
- *
- * @hide
- */
- @SystemApi
- public static final String NAMESPACE_WEARABLE_SENSING =
- "wearable_sensing";
-
- /**
- * Namespace for Vendor System Native related features.
- *
- * @hide
- */
- public static final String NAMESPACE_VENDOR_SYSTEM_NATIVE = "vendor_system_native";
-
- /**
- * Namespace for Vendor System Native Boot related features.
- *
- * @hide
- */
- public static final String NAMESPACE_VENDOR_SYSTEM_NATIVE_BOOT = "vendor_system_native_boot";
-
- /**
- * Namespace for memory safety related features (e.g. MTE)
- *
- * @hide
- */
- public static final String NAMESPACE_MEMORY_SAFETY_NATIVE = "memory_safety_native";
-
- /**
- * Namespace for wear OS platform features.
- *
- * @hide
- */
- public static final String NAMESPACE_WEAR = "wear";
-
- /**
- * Namespace for features relating to MBA transparency metadata.
- *
- * @hide
- */
- @SystemApi
- public static final String NAMESPACE_TRANSPARENCY_METADATA = "transparency_metadata";
-
- /**
- * Namespace for the input method manager platform features.
- *
- * @hide
- */
- @TestApi
- public static final String NAMESPACE_INPUT_METHOD_MANAGER = "input_method_manager";
-
- /**
- * Namespace for backup and restore service related features.
- *
- * @hide
- */
- @SystemApi
- public static final String NAMESPACE_BACKUP_AND_RESTORE = "backup_and_restore";
-
- /**
- * Namespace for ARC App Compat related features.
- *
- * @hide
- */
- public static final String NAMESPACE_ARC_APP_COMPAT = "arc_app_compat";
-
- private static final Object sLock = new Object();
- @GuardedBy("sLock")
- private static ArrayMap<OnPropertiesChangedListener, Pair<String, Executor>> sListeners =
- new ArrayMap<>();
- @GuardedBy("sLock")
- private static Map<String, Pair<ContentObserver, Integer>> sNamespaces = new HashMap<>();
- private static final String TAG = "DeviceConfig";
-
- // Should never be invoked
- private DeviceConfig() {
- }
-
- /**
- * Look up the value of a property for a particular namespace.
- *
- * @param namespace The namespace containing the property to look up.
- * @param name The name of the property to look up.
- * @return the corresponding value, or null if not present.
- * @hide
- */
- @SystemApi
- @RequiresPermission(READ_DEVICE_CONFIG)
- public static String getProperty(@NonNull String namespace, @NonNull String name) {
- // Fetch all properties for the namespace at once and cache them in the local process, so we
- // incur the cost of the IPC less often. Lookups happen much more frequently than updates,
- // and we want to optimize the former.
- return getProperties(namespace, name).getString(name, null);
- }
-
- /**
- * Look up the values of multiple properties for a particular namespace. The lookup is atomic,
- * such that the values of these properties cannot change between the time when the first is
- * fetched and the time when the last is fetched.
- * <p>
- * Each call to {@link #setProperties(Properties)} is also atomic and ensures that either none
- * or all of the change is picked up here, but never only part of it.
- *
- * @param namespace The namespace containing the properties to look up.
- * @param names The names of properties to look up, or empty to fetch all properties for the
- * given namespace.
- * @return {@link Properties} object containing the requested properties. This reflects the
- * state of these properties at the time of the lookup, and is not updated to reflect any
- * future changes. The keyset of this Properties object will contain only the intersection
- * of properties already set and properties requested via the names parameter. Properties
- * that are already set but were not requested will not be contained here. Properties that
- * are not set, but were requested will not be contained here either.
- * @hide
- */
- @SystemApi
- @NonNull
- @RequiresPermission(READ_DEVICE_CONFIG)
- public static Properties getProperties(@NonNull String namespace, @NonNull String ... names) {
- return new Properties(namespace,
- Settings.Config.getStrings(namespace, Arrays.asList(names)));
- }
-
- /**
- * Look up the String value of a property for a particular namespace.
- *
- * @param namespace The namespace containing the property to look up.
- * @param name The name of the property to look up.
- * @param defaultValue The value to return if the property does not exist or has no non-null
- * value.
- * @return the corresponding value, or defaultValue if none exists.
- * @hide
- */
- @SystemApi
- @RequiresPermission(READ_DEVICE_CONFIG)
- public static String getString(@NonNull String namespace, @NonNull String name,
- @Nullable String defaultValue) {
- String value = getProperty(namespace, name);
- return value != null ? value : defaultValue;
- }
-
- /**
- * Look up the boolean value of a property for a particular namespace.
- *
- * @param namespace The namespace containing the property to look up.
- * @param name The name of the property to look up.
- * @param defaultValue The value to return if the property does not exist or has no non-null
- * value.
- * @return the corresponding value, or defaultValue if none exists.
- * @hide
- */
- @SystemApi
- @RequiresPermission(READ_DEVICE_CONFIG)
- public static boolean getBoolean(@NonNull String namespace, @NonNull String name,
- boolean defaultValue) {
- String value = getProperty(namespace, name);
- return value != null ? Boolean.parseBoolean(value) : defaultValue;
- }
-
- /**
- * Look up the int value of a property for a particular namespace.
- *
- * @param namespace The namespace containing the property to look up.
- * @param name The name of the property to look up.
- * @param defaultValue The value to return if the property does not exist, has no non-null
- * value, or fails to parse into an int.
- * @return the corresponding value, or defaultValue if either none exists or it does not parse.
- * @hide
- */
- @SystemApi
- @RequiresPermission(READ_DEVICE_CONFIG)
- public static int getInt(@NonNull String namespace, @NonNull String name, int defaultValue) {
- String value = getProperty(namespace, name);
- if (value == null) {
- return defaultValue;
- }
- try {
- return Integer.parseInt(value);
- } catch (NumberFormatException e) {
- Log.e(TAG, "Parsing integer failed for " + namespace + ":" + name);
- return defaultValue;
- }
- }
-
- /**
- * Look up the long value of a property for a particular namespace.
- *
- * @param namespace The namespace containing the property to look up.
- * @param name The name of the property to look up.
- * @param defaultValue The value to return if the property does not exist, has no non-null
- * value, or fails to parse into a long.
- * @return the corresponding value, or defaultValue if either none exists or it does not parse.
- * @hide
- */
- @SystemApi
- @RequiresPermission(READ_DEVICE_CONFIG)
- public static long getLong(@NonNull String namespace, @NonNull String name, long defaultValue) {
- String value = getProperty(namespace, name);
- if (value == null) {
- return defaultValue;
- }
- try {
- return Long.parseLong(value);
- } catch (NumberFormatException e) {
- Log.e(TAG, "Parsing long failed for " + namespace + ":" + name);
- return defaultValue;
- }
- }
-
- /**
- * Look up the float value of a property for a particular namespace.
- *
- * @param namespace The namespace containing the property to look up.
- * @param name The name of the property to look up.
- * @param defaultValue The value to return if the property does not exist, has no non-null
- * value, or fails to parse into a float.
- * @return the corresponding value, or defaultValue if either none exists or it does not parse.
- * @hide
- */
- @SystemApi
- @RequiresPermission(READ_DEVICE_CONFIG)
- public static float getFloat(@NonNull String namespace, @NonNull String name,
- float defaultValue) {
- String value = getProperty(namespace, name);
- if (value == null) {
- return defaultValue;
- }
- try {
- return Float.parseFloat(value);
- } catch (NumberFormatException e) {
- Log.e(TAG, "Parsing float failed for " + namespace + ":" + name);
- return defaultValue;
- }
- }
-
- /**
- * Create a new property with the provided name and value in the provided namespace, or
- * update the value of such a property if it already exists. The same name can exist in multiple
- * namespaces and might have different values in any or all namespaces.
- * <p>
- * The method takes an argument indicating whether to make the value the default for this
- * property.
- * <p>
- * All properties stored for a particular scope can be reverted to their default values
- * by passing the namespace to {@link #resetToDefaults(int, String)}.
- *
- * @param namespace The namespace containing the property to create or update.
- * @param name The name of the property to create or update.
- * @param value The value to store for the property.
- * @param makeDefault Whether to make the new value the default one.
- * @return {@code true} if the value was set, {@code false} if the storage implementation throws
- * errors.
- * @hide
- * @see #resetToDefaults(int, String).
- */
- @SystemApi
- @RequiresPermission(WRITE_DEVICE_CONFIG)
- public static boolean setProperty(@NonNull String namespace, @NonNull String name,
- @Nullable String value, boolean makeDefault) {
- return Settings.Config.putString(namespace, name, value, makeDefault);
- }
-
- /**
- * Set all of the properties for a specific namespace. Pre-existing properties will be updated
- * and new properties will be added if necessary. Any pre-existing properties for the specific
- * namespace which are not part of the provided {@link Properties} object will be deleted from
- * the namespace. These changes are all applied atomically, such that no calls to read or reset
- * these properties can happen in the middle of this update.
- * <p>
- * Each call to {@link #getProperties(String, String...)} is also atomic and ensures that either
- * none or all of this update is picked up, but never only part of it.
- *
- * @param properties the complete set of properties to set for a specific namespace.
- * @throws BadConfigException if the provided properties are banned by RescueParty.
- * @return {@code true} if the values were set, {@code false} otherwise.
- * @hide
- */
- @SystemApi
- @RequiresPermission(WRITE_DEVICE_CONFIG)
- public static boolean setProperties(@NonNull Properties properties) throws BadConfigException {
- return Settings.Config.setStrings(properties.getNamespace(),
- properties.mMap);
- }
-
- /**
- * Delete a property with the provided name and value in the provided namespace
- *
- * @param namespace The namespace containing the property to delete.
- * @param name The name of the property to delete.
- * @return {@code true} if the property was deleted or it did not exist in the first place.
- * Return {@code false} if the storage implementation throws errors.
- * @hide
- */
- @SystemApi
- @RequiresPermission(WRITE_DEVICE_CONFIG)
- public static boolean deleteProperty(@NonNull String namespace, @NonNull String name) {
- return Settings.Config.deleteString(namespace, name);
- }
-
- /**
- * Reset properties to their default values by removing the underlying values.
- * <p>
- * The method accepts an optional namespace parameter. If provided, only properties set within
- * that namespace will be reset. Otherwise, all properties will be reset.
- * <p>
- * Note: This method should only be used by {@link com.android.server.RescueParty}. It was
- * designed to be used in the event of boot or crash loops caused by flag changes. It does not
- * revert flag values to defaults - instead it removes the property entirely which causes the
- * consumer of the flag to use hardcoded defaults upon retrieval.
- * <p>
- * To clear values for a namespace without removing the underlying properties, construct a
- * {@link Properties} object with the caller's namespace and either an empty flag map, or some
- * snapshot of flag values. Then use {@link #setProperties(Properties)} to remove all flags
- * under the namespace, or set them to the values in the snapshot.
- * <p>
- * To revert values for testing, one should mock DeviceConfig using
- * {@link com.android.server.testables.TestableDeviceConfig} where possible. Otherwise, fallback
- * to using {@link #setProperties(Properties)} as outlined above.
- *
- * @param resetMode The reset mode to use.
- * @param namespace Optionally, the specific namespace which resets will be limited to.
- * @hide
- * @see #setProperty(String, String, String, boolean)
- */
- @SystemApi
- @RequiresPermission(WRITE_DEVICE_CONFIG)
- public static void resetToDefaults(@ResetMode int resetMode, @Nullable String namespace) {
- Settings.Config.resetToDefaults(resetMode, namespace);
- }
-
- /**
- * Disables or re-enables bulk modifications ({@link #setProperties(Properties)}) to device
- * config values. This is intended for use during tests to prevent a sync operation clearing
- * config values which could influence the outcome of the tests, i.e. by changing behavior.
- *
- * @param syncDisabledMode the mode to use, see {@link Settings.Config#SYNC_DISABLED_MODE_NONE},
- * {@link Settings.Config#SYNC_DISABLED_MODE_PERSISTENT} and {@link
- * Settings.Config#SYNC_DISABLED_MODE_UNTIL_REBOOT}
- *
- * @see #getSyncDisabledMode()
- * @hide
- */
- @RequiresPermission(WRITE_DEVICE_CONFIG)
- public static void setSyncDisabledMode(@SyncDisabledMode int syncDisabledMode) {
- Settings.Config.setSyncDisabledMode(syncDisabledMode);
- }
-
- /**
- * Returns the current mode of sync disabling.
- *
- * @see #setSyncDisabledMode(int)
- * @hide
- */
- @RequiresPermission(WRITE_DEVICE_CONFIG)
- public static @SyncDisabledMode int getSyncDisabledMode() {
- return Settings.Config.getSyncDisabledMode();
- }
-
- /**
- * Add a listener for property changes.
- * <p>
- * This listener will be called whenever properties in the specified namespace change. Callbacks
- * will be made on the specified executor. Future calls to this method with the same listener
- * will replace the old namespace and executor. Remove the listener entirely by calling
- * {@link #removeOnPropertiesChangedListener(OnPropertiesChangedListener)}.
- *
- * @param namespace The namespace containing properties to monitor.
- * @param executor The executor which will be used to run callbacks.
- * @param onPropertiesChangedListener The listener to add.
- * @hide
- * @see #removeOnPropertiesChangedListener(OnPropertiesChangedListener)
- */
- @SystemApi
- @RequiresPermission(READ_DEVICE_CONFIG)
- public static void addOnPropertiesChangedListener(
- @NonNull String namespace,
- @NonNull @CallbackExecutor Executor executor,
- @NonNull OnPropertiesChangedListener onPropertiesChangedListener) {
- enforceReadPermission(namespace);
- synchronized (sLock) {
- Pair<String, Executor> oldNamespace = sListeners.get(onPropertiesChangedListener);
- if (oldNamespace == null) {
- // Brand new listener, add it to the list.
- sListeners.put(onPropertiesChangedListener, new Pair<>(namespace, executor));
- incrementNamespace(namespace);
- } else if (namespace.equals(oldNamespace.first)) {
- // Listener is already registered for this namespace, update executor just in case.
- sListeners.put(onPropertiesChangedListener, new Pair<>(namespace, executor));
- } else {
- // Update this listener from an old namespace to the new one.
- decrementNamespace(sListeners.get(onPropertiesChangedListener).first);
- sListeners.put(onPropertiesChangedListener, new Pair<>(namespace, executor));
- incrementNamespace(namespace);
- }
- }
- }
-
- /**
- * Remove a listener for property changes. The listener will receive no further notification of
- * property changes.
- *
- * @param onPropertiesChangedListener The listener to remove.
- * @hide
- * @see #addOnPropertiesChangedListener(String, Executor, OnPropertiesChangedListener)
- */
- @SystemApi
- public static void removeOnPropertiesChangedListener(
- @NonNull OnPropertiesChangedListener onPropertiesChangedListener) {
- Preconditions.checkNotNull(onPropertiesChangedListener);
- synchronized (sLock) {
- if (sListeners.containsKey(onPropertiesChangedListener)) {
- decrementNamespace(sListeners.get(onPropertiesChangedListener).first);
- sListeners.remove(onPropertiesChangedListener);
- }
- }
- }
-
- private static Uri createNamespaceUri(@NonNull String namespace) {
- Preconditions.checkNotNull(namespace);
- return CONTENT_URI.buildUpon().appendPath(namespace).build();
- }
-
- /**
- * Increment the count used to represent the number of listeners subscribed to the given
- * namespace. If this is the first (i.e. incrementing from 0 to 1) for the given namespace, a
- * ContentObserver is registered.
- *
- * @param namespace The namespace to increment the count for.
- */
- @GuardedBy("sLock")
- private static void incrementNamespace(@NonNull String namespace) {
- Preconditions.checkNotNull(namespace);
- Pair<ContentObserver, Integer> namespaceCount = sNamespaces.get(namespace);
- if (namespaceCount != null) {
- sNamespaces.put(namespace, new Pair<>(namespaceCount.first, namespaceCount.second + 1));
- } else {
- // This is a new namespace, register a ContentObserver for it.
- ContentObserver contentObserver = new ContentObserver(null) {
- @Override
- public void onChange(boolean selfChange, Uri uri) {
- if (uri != null) {
- handleChange(uri);
- }
- }
- };
- Settings.Config
- .registerContentObserver(createNamespaceUri(namespace), true, contentObserver);
- sNamespaces.put(namespace, new Pair<>(contentObserver, 1));
- }
- }
-
- /**
- * Decrement the count used to represent the number of listeners subscribed to the given
- * namespace. If this is the final decrement call (i.e. decrementing from 1 to 0) for the given
- * namespace, the ContentObserver that had been tracking it will be removed.
- *
- * @param namespace The namespace to decrement the count for.
- */
- @GuardedBy("sLock")
- private static void decrementNamespace(@NonNull String namespace) {
- Preconditions.checkNotNull(namespace);
- Pair<ContentObserver, Integer> namespaceCount = sNamespaces.get(namespace);
- if (namespaceCount == null) {
- // This namespace is not registered and does not need to be decremented
- return;
- } else if (namespaceCount.second > 1) {
- sNamespaces.put(namespace, new Pair<>(namespaceCount.first, namespaceCount.second - 1));
- } else {
- // Decrementing a namespace to zero means we no longer need its ContentObserver.
- Settings.Config.unregisterContentObserver(namespaceCount.first);
- sNamespaces.remove(namespace);
- }
- }
-
- private static void handleChange(@NonNull Uri uri) {
- Preconditions.checkNotNull(uri);
- List<String> pathSegments = uri.getPathSegments();
- // pathSegments(0) is "config"
- final String namespace = pathSegments.get(1);
- Properties.Builder propBuilder = new Properties.Builder(namespace);
- try {
- Properties allProperties = getProperties(namespace);
- for (int i = 2; i < pathSegments.size(); ++i) {
- String key = pathSegments.get(i);
- propBuilder.setString(key, allProperties.getString(key, null));
- }
- } catch (SecurityException e) {
- // Silently failing to not crash binder or listener threads.
- Log.e(TAG, "OnPropertyChangedListener update failed: permission violation.");
- return;
- }
- Properties properties = propBuilder.build();
-
- synchronized (sLock) {
- for (int i = 0; i < sListeners.size(); i++) {
- if (namespace.equals(sListeners.valueAt(i).first)) {
- final OnPropertiesChangedListener listener = sListeners.keyAt(i);
- sListeners.valueAt(i).second.execute(() -> {
- listener.onPropertiesChanged(properties);
- });
- }
- }
- }
- }
-
- /**
- * Enforces READ_DEVICE_CONFIG permission if namespace is not one of public namespaces.
- * @hide
- */
- public static void enforceReadPermission(String namespace) {
- if (Settings.Config.checkCallingOrSelfPermission(READ_DEVICE_CONFIG)
- != PackageManager.PERMISSION_GRANTED) {
- if (!PUBLIC_NAMESPACES.contains(namespace)) {
- throw new SecurityException("Permission denial: reading from settings requires:"
- + READ_DEVICE_CONFIG);
- }
- }
- }
-
- /**
- * Returns list of namespaces that can be read without READ_DEVICE_CONFIG_PERMISSION;
- * @hide
- */
- public static @NonNull List<String> getPublicNamespaces() {
- return PUBLIC_NAMESPACES;
- }
-
- /**
- * Interface for monitoring changes to properties. Implementations will receive callbacks when
- * properties change, including a {@link Properties} object which contains a single namespace
- * and all of the properties which changed for that namespace. This includes properties which
- * were added, updated, or deleted. This is not necessarily a complete list of all properties
- * belonging to the namespace, as properties which don't change are omitted.
- * <p>
- * Override {@link #onPropertiesChanged(Properties)} to handle callbacks for changes.
- *
- * @hide
- */
- @SystemApi
- public interface OnPropertiesChangedListener {
- /**
- * Called when one or more properties have changed, providing a Properties object with all
- * of the changed properties. This object will contain only properties which have changed,
- * not the complete set of all properties belonging to the namespace.
- *
- * @param properties Contains the complete collection of properties which have changed for a
- * single namespace. This includes only those which were added, updated,
- * or deleted.
- */
- void onPropertiesChanged(@NonNull Properties properties);
- }
-
- /**
- * Thrown by {@link #setProperties(Properties)} when a configuration is rejected. This
- * happens if RescueParty has identified a bad configuration and reset the namespace.
- *
- * @hide
- */
- @SystemApi
- public static class BadConfigException extends Exception {}
-
- /**
- * A mapping of properties to values, as well as a single namespace which they all belong to.
- *
- * @hide
- */
- @SystemApi
- public static class Properties {
- private final String mNamespace;
- private final HashMap<String, String> mMap;
- private Set<String> mKeyset;
-
- /**
- * Create a mapping of properties to values and the namespace they belong to.
- *
- * @param namespace The namespace these properties belong to.
- * @param keyValueMap A map between property names and property values.
- * @hide
- */
- public Properties(@NonNull String namespace, @Nullable Map<String, String> keyValueMap) {
- Preconditions.checkNotNull(namespace);
- mNamespace = namespace;
- mMap = new HashMap();
- if (keyValueMap != null) {
- mMap.putAll(keyValueMap);
- }
- }
-
- /**
- * @return the namespace all properties within this instance belong to.
- */
- @NonNull
- public String getNamespace() {
- return mNamespace;
- }
-
- /**
- * @return the non-null set of property names.
- */
- @NonNull
- public Set<String> getKeyset() {
- if (mKeyset == null) {
- mKeyset = Collections.unmodifiableSet(mMap.keySet());
- }
- return mKeyset;
- }
-
- /**
- * Look up the String value of a property.
- *
- * @param name The name of the property to look up.
- * @param defaultValue The value to return if the property has not been defined.
- * @return the corresponding value, or defaultValue if none exists.
- */
- @Nullable
- public String getString(@NonNull String name, @Nullable String defaultValue) {
- Preconditions.checkNotNull(name);
- String value = mMap.get(name);
- return value != null ? value : defaultValue;
- }
-
- /**
- * Look up the boolean value of a property.
- *
- * @param name The name of the property to look up.
- * @param defaultValue The value to return if the property has not been defined.
- * @return the corresponding value, or defaultValue if none exists.
- */
- public boolean getBoolean(@NonNull String name, boolean defaultValue) {
- Preconditions.checkNotNull(name);
- String value = mMap.get(name);
- return value != null ? Boolean.parseBoolean(value) : defaultValue;
- }
-
- /**
- * Look up the int value of a property.
- *
- * @param name The name of the property to look up.
- * @param defaultValue The value to return if the property has not been defined or fails to
- * parse into an int.
- * @return the corresponding value, or defaultValue if no valid int is available.
- */
- public int getInt(@NonNull String name, int defaultValue) {
- Preconditions.checkNotNull(name);
- String value = mMap.get(name);
- if (value == null) {
- return defaultValue;
- }
- try {
- return Integer.parseInt(value);
- } catch (NumberFormatException e) {
- Log.e(TAG, "Parsing int failed for " + name);
- return defaultValue;
- }
- }
-
- /**
- * Look up the long value of a property.
- *
- * @param name The name of the property to look up.
- * @param defaultValue The value to return if the property has not been defined. or fails to
- * parse into a long.
- * @return the corresponding value, or defaultValue if no valid long is available.
- */
- public long getLong(@NonNull String name, long defaultValue) {
- Preconditions.checkNotNull(name);
- String value = mMap.get(name);
- if (value == null) {
- return defaultValue;
- }
- try {
- return Long.parseLong(value);
- } catch (NumberFormatException e) {
- Log.e(TAG, "Parsing long failed for " + name);
- return defaultValue;
- }
- }
-
- /**
- * Look up the int value of a property.
- *
- * @param name The name of the property to look up.
- * @param defaultValue The value to return if the property has not been defined. or fails to
- * parse into a float.
- * @return the corresponding value, or defaultValue if no valid float is available.
- */
- public float getFloat(@NonNull String name, float defaultValue) {
- Preconditions.checkNotNull(name);
- String value = mMap.get(name);
- if (value == null) {
- return defaultValue;
- }
- try {
- return Float.parseFloat(value);
- } catch (NumberFormatException e) {
- Log.e(TAG, "Parsing float failed for " + name);
- return defaultValue;
- }
- }
-
- /**
- * Builder class for the construction of {@link Properties} objects.
- */
- public static final class Builder {
- @NonNull
- private final String mNamespace;
- @NonNull
- private final Map<String, String> mKeyValues = new HashMap<>();
-
- /**
- * Create a new Builders for the specified namespace.
- * @param namespace non null namespace.
- */
- public Builder(@NonNull String namespace) {
- mNamespace = namespace;
- }
-
- /**
- * Add a new property with the specified key and value.
- * @param name non null name of the property.
- * @param value nullable string value of the property.
- * @return this Builder object
- */
- @NonNull
- public Builder setString(@NonNull String name, @Nullable String value) {
- mKeyValues.put(name, value);
- return this;
- }
-
- /**
- * Add a new property with the specified key and value.
- * @param name non null name of the property.
- * @param value nullable string value of the property.
- * @return this Builder object
- */
- @NonNull
- public Builder setBoolean(@NonNull String name, boolean value) {
- mKeyValues.put(name, Boolean.toString(value));
- return this;
- }
-
- /**
- * Add a new property with the specified key and value.
- * @param name non null name of the property.
- * @param value int value of the property.
- * @return this Builder object
- */
- @NonNull
- public Builder setInt(@NonNull String name, int value) {
- mKeyValues.put(name, Integer.toString(value));
- return this;
- }
-
- /**
- * Add a new property with the specified key and value.
- * @param name non null name of the property.
- * @param value long value of the property.
- * @return this Builder object
- */
- @NonNull
- public Builder setLong(@NonNull String name, long value) {
- mKeyValues.put(name, Long.toString(value));
- return this;
- }
-
- /**
- * Add a new property with the specified key and value.
- * @param name non null name of the property.
- * @param value float value of the property.
- * @return this Builder object
- */
- @NonNull
- public Builder setFloat(@NonNull String name, float value) {
- mKeyValues.put(name, Float.toString(value));
- return this;
- }
-
- /**
- * Create a new {@link Properties} object.
- * @return non null Properties.
- */
- @NonNull
- public Properties build() {
- return new Properties(mNamespace, mKeyValues);
- }
- }
- }
-
-}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 2adbbcd454e5..eeac00b41467 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -599,8 +599,8 @@ public final class Settings {
* the result is set to {@link android.app.Activity#RESULT_CANCELED}.
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
- public static final String ACTION_MANAGE_APP_LONG_JOBS =
- "android.settings.MANAGE_APP_LONG_JOBS";
+ public static final String ACTION_MANAGE_APP_LONG_RUNNING_JOBS =
+ "android.settings.MANAGE_APP_LONG_RUNNING_JOBS";
/**
* Activity Action: Show settings to allow configuration of cross-profile access for apps
@@ -2819,6 +2819,7 @@ public final class Settings {
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@TestApi
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final int RESET_MODE_PACKAGE_DEFAULTS = 1;
/**
@@ -2828,6 +2829,7 @@ public final class Settings {
* the setting will be deleted. This mode is only available to the system.
* @hide
*/
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final int RESET_MODE_UNTRUSTED_DEFAULTS = 2;
/**
@@ -2838,6 +2840,7 @@ public final class Settings {
* This mode is only available to the system.
* @hide
*/
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final int RESET_MODE_UNTRUSTED_CHANGES = 3;
/**
@@ -2849,6 +2852,7 @@ public final class Settings {
* to the system.
* @hide
*/
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final int RESET_MODE_TRUSTED_DEFAULTS = 4;
/** @hide */
@@ -3362,7 +3366,7 @@ public final class Settings {
public ArrayMap<String, String> getStringsForPrefix(ContentResolver cr, String prefix,
List<String> names) {
String namespace = prefix.substring(0, prefix.length() - 1);
- DeviceConfig.enforceReadPermission(namespace);
+ Config.enforceReadPermission(namespace);
ArrayMap<String, String> keyValues = new ArrayMap<>();
int currentGeneration = -1;
@@ -7224,6 +7228,15 @@ public final class Settings {
public static final String SHOW_IME_WITH_HARD_KEYBOARD = "show_ime_with_hard_keyboard";
/**
+ * Whether stylus button presses are disabled. This is a boolean that
+ * determines if stylus buttons are ignored.
+ *
+ * @hide
+ */
+ @SuppressLint("NoSettingsProvider")
+ public static final String STYLUS_BUTTONS_DISABLED = "stylus_buttons_disabled";
+
+ /**
* Host name and port for global http proxy. Uses ':' seperator for
* between host and port.
*
@@ -9357,6 +9370,14 @@ public final class Settings {
public static final int DOCK_SETUP_PAUSED = 2;
/**
+ * Indicates that the user has been prompted to start dock setup.
+ * One of the possible states for {@link #DOCK_SETUP_STATE}.
+ *
+ * @hide
+ */
+ public static final int DOCK_SETUP_PROMPTED = 3;
+
+ /**
* Indicates that the user has completed dock setup.
* One of the possible states for {@link #DOCK_SETUP_STATE}.
*
@@ -9370,6 +9391,7 @@ public final class Settings {
DOCK_SETUP_NOT_STARTED,
DOCK_SETUP_STARTED,
DOCK_SETUP_PAUSED,
+ DOCK_SETUP_PROMPTED,
DOCK_SETUP_COMPLETED
})
public @interface DockSetupState {
@@ -17935,6 +17957,12 @@ public final class Settings {
public static final String WET_MODE_ON = "wet_mode_on";
/*
+ * Whether the RSB wake feature is enabled.
+ * @hide
+ */
+ public static final String RSB_WAKE_ENABLED = "rsb_wake_enabled";
+
+ /*
* Whether the screen-unlock (keyguard) sound is enabled.
* @hide
*/
@@ -17986,6 +18014,7 @@ public final class Settings {
*
* @hide
*/
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final class Config extends NameValueTable {
/**
@@ -18020,12 +18049,19 @@ public final class Settings {
*/
public static final int SYNC_DISABLED_MODE_UNTIL_REBOOT = 2;
+ /**
+ * The content:// style URL for the config table.
+ *
+ * @hide
+ */
+ public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/config");
+
private static final ContentProviderHolder sProviderHolder =
- new ContentProviderHolder(DeviceConfig.CONTENT_URI);
+ new ContentProviderHolder(CONTENT_URI);
// Populated lazily, guarded by class object:
private static final NameValueCache sNameValueCache = new NameValueCache(
- DeviceConfig.CONTENT_URI,
+ CONTENT_URI,
CALL_METHOD_GET_CONFIG,
CALL_METHOD_PUT_CONFIG,
CALL_METHOD_DELETE_CONFIG,
@@ -18034,6 +18070,10 @@ public final class Settings {
sProviderHolder,
Config.class);
+ // Should never be invoked
+ private Config() {
+ }
+
/**
* Look up a name in the database.
* @param name to look up in the table
@@ -18041,8 +18081,10 @@ public final class Settings {
*
* @hide
*/
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @Nullable
@RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
- static String getString(String name) {
+ public static String getString(@NonNull String name) {
ContentResolver resolver = getContentResolver();
return sNameValueCache.getStringForUser(resolver, name, resolver.getUserId());
}
@@ -18057,6 +18099,8 @@ public final class Settings {
*
* @hide
*/
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @NonNull
@RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
public static Map<String, String> getStrings(@NonNull String namespace,
@NonNull List<String> names) {
@@ -18113,6 +18157,7 @@ public final class Settings {
*
* @hide
*/
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
@RequiresPermission(Manifest.permission.WRITE_DEVICE_CONFIG)
public static boolean putString(@NonNull String namespace,
@NonNull String name, @Nullable String value, boolean makeDefault) {
@@ -18132,6 +18177,7 @@ public final class Settings {
*
* @hide
*/
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
@RequiresPermission(Manifest.permission.WRITE_DEVICE_CONFIG)
public static boolean setStrings(@NonNull String namespace,
@NonNull Map<String, String> keyValues)
@@ -18182,8 +18228,9 @@ public final class Settings {
*
* @hide
*/
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
@RequiresPermission(Manifest.permission.WRITE_DEVICE_CONFIG)
- static boolean deleteString(@NonNull String namespace,
+ public static boolean deleteString(@NonNull String namespace,
@NonNull String name) {
ContentResolver resolver = getContentResolver();
return sNameValueCache.deleteStringForUser(resolver,
@@ -18203,8 +18250,9 @@ public final class Settings {
*
* @hide
*/
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
@RequiresPermission(Manifest.permission.WRITE_DEVICE_CONFIG)
- static void resetToDefaults(@ResetMode int resetMode,
+ public static void resetToDefaults(@ResetMode int resetMode,
@Nullable String namespace) {
try {
ContentResolver resolver = getContentResolver();
@@ -18218,7 +18266,7 @@ public final class Settings {
cp.call(resolver.getAttributionSource(),
sProviderHolder.mUri.getAuthority(), CALL_METHOD_RESET_CONFIG, null, arg);
} catch (RemoteException e) {
- Log.w(TAG, "Can't reset to defaults for " + DeviceConfig.CONTENT_URI, e);
+ Log.w(TAG, "Can't reset to defaults for " + CONTENT_URI, e);
}
}
@@ -18228,9 +18276,10 @@ public final class Settings {
*
* @hide
*/
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
@SuppressLint("AndroidFrameworkRequiresPermission")
@RequiresPermission(Manifest.permission.WRITE_DEVICE_CONFIG)
- static void setSyncDisabledMode(@SyncDisabledMode int disableSyncMode) {
+ public static void setSyncDisabledMode(@SyncDisabledMode int disableSyncMode) {
try {
ContentResolver resolver = getContentResolver();
Bundle args = new Bundle();
@@ -18239,7 +18288,7 @@ public final class Settings {
cp.call(resolver.getAttributionSource(), sProviderHolder.mUri.getAuthority(),
CALL_METHOD_SET_SYNC_DISABLED_MODE_CONFIG, null, args);
} catch (RemoteException e) {
- Log.w(TAG, "Can't set sync disabled mode " + DeviceConfig.CONTENT_URI, e);
+ Log.w(TAG, "Can't set sync disabled mode " + CONTENT_URI, e);
}
}
@@ -18249,9 +18298,10 @@ public final class Settings {
*
* @hide
*/
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
@SuppressLint("AndroidFrameworkRequiresPermission")
@RequiresPermission(Manifest.permission.WRITE_DEVICE_CONFIG)
- static int getSyncDisabledMode() {
+ public static int getSyncDisabledMode() {
try {
ContentResolver resolver = getContentResolver();
Bundle args = Bundle.EMPTY;
@@ -18262,7 +18312,7 @@ public final class Settings {
null, args);
return bundle.getInt(KEY_CONFIG_GET_SYNC_DISABLED_MODE_RETURN);
} catch (RemoteException e) {
- Log.w(TAG, "Can't query sync disabled mode " + DeviceConfig.CONTENT_URI, e);
+ Log.w(TAG, "Can't query sync disabled mode " + CONTENT_URI, e);
}
return -1;
}
@@ -18282,21 +18332,26 @@ public final class Settings {
/**
- * Register a content observer
+ * Register a content observer.
*
* @hide
*/
- public static void registerContentObserver(@NonNull Uri uri, boolean notifyForDescendants,
- @NonNull ContentObserver observer) {
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static void registerContentObserver(@Nullable String namespace,
+ boolean notifyForDescendants, @NonNull ContentObserver observer) {
ActivityThread.currentApplication().getContentResolver()
- .registerContentObserver(uri, notifyForDescendants, observer);
+ .registerContentObserver(createNamespaceUri(namespace),
+ notifyForDescendants, observer);
}
/**
- * Unregister a content observer
+ * Unregister a content observer.
+ * this may only be used with content observers registered through
+ * {@link Config#registerContentObserver}
*
* @hide
*/
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static void unregisterContentObserver(@NonNull ContentObserver observer) {
ActivityThread.currentApplication().getContentResolver()
.unregisterContentObserver(observer);
@@ -18324,6 +18379,21 @@ public final class Settings {
.getApplicationContext().checkCallingOrSelfPermission(permission);
}
+ /**
+ * Enforces READ_DEVICE_CONFIG permission if namespace is not one of public namespaces.
+ * @hide
+ */
+ public static void enforceReadPermission(String namespace) {
+ if (ActivityThread.currentApplication().getApplicationContext()
+ .checkCallingOrSelfPermission(Manifest.permission.READ_DEVICE_CONFIG)
+ != PackageManager.PERMISSION_GRANTED) {
+ if (!DeviceConfig.getPublicNamespaces().contains(namespace)) {
+ throw new SecurityException("Permission denial: reading from settings requires:"
+ + Manifest.permission.READ_DEVICE_CONFIG);
+ }
+ }
+ }
+
private static void registerMonitorCallbackAsUser(
@NonNull ContentResolver resolver, @UserIdInt int userHandle,
@NonNull RemoteCallback callback) {
@@ -18357,6 +18427,11 @@ public final class Settings {
return namespace + "/";
}
+ private static Uri createNamespaceUri(@NonNull String namespace) {
+ Preconditions.checkNotNull(namespace);
+ return CONTENT_URI.buildUpon().appendPath(namespace).build();
+ }
+
private static ContentResolver getContentResolver() {
return ActivityThread.currentApplication().getContentResolver();
}
diff --git a/core/java/android/security/keymaster/KeymasterDefs.java b/core/java/android/security/keymaster/KeymasterDefs.java
index e720f1ab1523..4d6422c670c2 100644
--- a/core/java/android/security/keymaster/KeymasterDefs.java
+++ b/core/java/android/security/keymaster/KeymasterDefs.java
@@ -126,6 +126,8 @@ public final class KeymasterDefs {
Tag.BOOT_PATCHLEVEL; // KM_UINT | 719;
public static final int KM_TAG_DEVICE_UNIQUE_ATTESTATION =
Tag.DEVICE_UNIQUE_ATTESTATION; // KM_BOOL | 720;
+ public static final int KM_TAG_ATTESTATION_ID_SECOND_IMEI =
+ Tag.ATTESTATION_ID_SECOND_IMEI; // KM_BYTES | 723;
public static final int KM_TAG_NONCE = Tag.NONCE; // KM_BYTES | 1001;
public static final int KM_TAG_MAC_LENGTH = Tag.MAC_LENGTH; // KM_UINT | 1003;
diff --git a/core/java/android/security/rkp/IGetKeyCallback.aidl b/core/java/android/security/rkp/IGetKeyCallback.aidl
new file mode 100644
index 000000000000..85ceae62aadc
--- /dev/null
+++ b/core/java/android/security/rkp/IGetKeyCallback.aidl
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.rkp;
+
+import android.security.rkp.RemotelyProvisionedKey;
+
+/**
+ * Callback interface for receiving remotely provisioned keys from a
+ * {@link IRegistration}.
+ *
+ * @hide
+ */
+oneway interface IGetKeyCallback {
+ /**
+ * Called in response to {@link IRegistration.getKey}, indicating
+ * a remotely-provisioned key is available.
+ *
+ * @param key The key that was received from the remote provisioning service.
+ */
+ void onSuccess(in RemotelyProvisionedKey key);
+
+ /**
+ * Called when the key request has been successfully cancelled.
+ * @see IRegistration.cancelGetKey
+ */
+ void onCancel();
+
+ /**
+ * Called when an error has occurred while trying to get a remotely provisioned key.
+ *
+ * @param error A description of what failed, suitable for logging.
+ */
+ void onError(String error);
+}
+
diff --git a/core/java/android/security/rkp/IGetRegistrationCallback.aidl b/core/java/android/security/rkp/IGetRegistrationCallback.aidl
new file mode 100644
index 000000000000..e375a6f97b31
--- /dev/null
+++ b/core/java/android/security/rkp/IGetRegistrationCallback.aidl
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.rkp;
+
+import android.security.rkp.IRegistration;
+
+/**
+ * Callback interface for receiving a remote provisioning registration.
+ * {@link IRegistration}.
+ *
+ * @hide
+ */
+oneway interface IGetRegistrationCallback {
+ /**
+ * Called in response to {@link IRemoteProvisioning.getRegistration}.
+ *
+ * @param registration an IRegistration that is used to fetch remotely
+ * provisioned keys for the given IRemotelyProvisionedComponent.
+ */
+ void onSuccess(in IRegistration registration);
+
+ /**
+ * Called when the get registration request has been successfully cancelled.
+ * @see IRemoteProvisioning.cancelGetRegistration
+ */
+ void onCancel();
+
+ /**
+ * Called when an error has occurred while trying to get a registration.
+ *
+ * @param error A description of what failed, suitable for logging.
+ */
+ void onError(String error);
+}
+
diff --git a/core/java/android/security/rkp/IRegistration.aidl b/core/java/android/security/rkp/IRegistration.aidl
new file mode 100644
index 000000000000..6522a458de4e
--- /dev/null
+++ b/core/java/android/security/rkp/IRegistration.aidl
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.rkp;
+
+import android.security.rkp.IGetKeyCallback;
+
+/**
+ * This interface is associated with the registration of an
+ * IRemotelyProvisionedComponent. Each component has a unique set of keys
+ * and certificates that are provisioned to the device for attestation. An
+ * IRegistration binder is created by calling
+ * {@link IRemoteProvisioning#getRegistration()}.
+ *
+ * This interface is used to query for available keys and certificates for the
+ * registered component.
+ *
+ * @hide
+ */
+oneway interface IRegistration {
+ /**
+ * Fetch a remotely provisioned key for the given keyId. Keys are unique
+ * per caller/keyId/registration tuple. This ensures that no two
+ * applications are able to correlate keys to uniquely identify a
+ * device/user. Callers receive their key via {@code callback}.
+ *
+ * If a key is available, this call immediately invokes {@code callback}.
+ *
+ * If no keys are immediately available, then this function contacts the
+ * remote provisioning server to provision a key. After provisioning is
+ * completed, the key is passed to {@code callback}.
+ *
+ * @param keyId This is a client-chosen key identifier, used to
+ * differentiate between keys for varying client-specific use-cases. For
+ * example, keystore2 passes the UID of the applications that call it as
+ * the keyId value here, so that each of keystore2's clients gets a unique
+ * key.
+ * @param callback Receives the result of the call. A callback must only
+ * be used with one {@code getKey} call at a time.
+ */
+ void getKey(int keyId, IGetKeyCallback callback);
+
+ /**
+ * Cancel an active request for a remotely provisioned key, as initiated via
+ * {@link getKey}. Upon cancellation, {@code callback.onCancel} will be invoked.
+ */
+ void cancelGetKey(IGetKeyCallback callback);
+
+ /**
+ * Replace an obsolete key blob with an upgraded key blob.
+ * In certain cases, such as security patch level upgrade, keys become "old".
+ * In these cases, the component which supports operations with the remotely
+ * provisioned key blobs must support upgrading the blobs to make them "new"
+ * and usable on the updated system.
+ *
+ * For an example of a remotely provisioned component that has an upgrade
+ * mechanism, see the documentation for IKeyMintDevice.upgradeKey.
+ *
+ * Once a key has been upgraded, the IRegistration where the key is stored
+ * needs to be told about the new blob. After calling storeUpgradedKey,
+ * getKey will return the new key blob instead of the old one.
+ *
+ * Note that this function does NOT extend the lifetime of key blobs. The
+ * certificate for the key is unchanged, and the key will still expire at
+ * the same time it would have if storeUpgradedKey had never been called.
+ *
+ * @param oldKeyBlob The old key blob to be replaced by {@code newKeyBlob}.
+ *
+ * @param newKeyblob The new blob to replace {@code oldKeyBlob}.
+ */
+ void storeUpgradedKey(in byte[] oldKeyBlob, in byte[] newKeyBlob);
+}
diff --git a/core/java/android/security/rkp/IRemoteProvisioning.aidl b/core/java/android/security/rkp/IRemoteProvisioning.aidl
new file mode 100644
index 000000000000..23d8159f7b28
--- /dev/null
+++ b/core/java/android/security/rkp/IRemoteProvisioning.aidl
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.rkp;
+
+import android.security.rkp.IRegistration;
+import android.security.rkp.IGetRegistrationCallback;
+
+/**
+ * {@link IRemoteProvisioning} is the interface provided to use the remote key
+ * provisioning functionality from the Remote Key Provisioning Daemon (RKPD).
+ * This would be the first service that RKPD clients would interact with. The
+ * intent is for the clients to get the {@link IRegistration} object from this
+ * interface and use it for actual remote provisioning work.
+ *
+ * @hide
+ */
+oneway interface IRemoteProvisioning {
+ /**
+ * Takes a remotely provisioned component service name and gets a
+ * registration bound to that service and the caller's UID.
+ *
+ * @param irpcName The name of the {@code IRemotelyProvisionedComponent}
+ * for which remotely provisioned keys should be managed.
+ * @param callback Receives the result of the call. A callback must only
+ * be used with one {@code getRegistration} call at a time.
+ *
+ * Notes:
+ * - This function will attempt to get the service named by irpcName. This
+ * implies that a lazy/dynamic aidl service will be instantiated, and this
+ * function blocks until the service is up. Upon return, any binder tokens
+ * are dropped, allowing the lazy/dynamic service to shutdown.
+ * - The created registration object is unique per caller. If two different
+ * UIDs call getRegistration with the same irpcName, they will receive
+ * different registrations. This prevents two different applications from
+ * being able to see the same keys.
+ * - This function is idempotent per calling UID. Additional calls to
+ * getRegistration with the same parameters, from the same caller, will have
+ * no side effects.
+ * - A callback may only be associated with one getRegistration call at a time.
+ * If the callback is used multiple times, this API will return an error.
+ *
+ * @see IRegistration#getKey()
+ * @see IRemotelyProvisionedComponent
+ *
+ */
+ void getRegistration(String irpcName, IGetRegistrationCallback callback);
+
+ /**
+ * Cancel any active {@link getRegistration} call associated with the given
+ * callback. If no getRegistration call is currently active, this function is
+ * a noop.
+ */
+ void cancelGetRegistration(IGetRegistrationCallback callback);
+}
diff --git a/core/java/android/security/rkp/OWNERS b/core/java/android/security/rkp/OWNERS
new file mode 100644
index 000000000000..fd430899fc5f
--- /dev/null
+++ b/core/java/android/security/rkp/OWNERS
@@ -0,0 +1,5 @@
+# Bug component: 1084908
+
+jbires@google.com
+sethmo@google.com
+vikramgaur@google.com
diff --git a/core/java/android/security/rkp/RemotelyProvisionedKey.aidl b/core/java/android/security/rkp/RemotelyProvisionedKey.aidl
new file mode 100644
index 000000000000..207f18f2f947
--- /dev/null
+++ b/core/java/android/security/rkp/RemotelyProvisionedKey.aidl
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2022, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.rkp;
+
+/**
+ * A {@link RemotelyProvisionedKey} holds an attestation key and the
+ * corresponding remotely provisioned certificate chain.
+ *
+ * @hide
+ */
+@RustDerive(Eq=true, PartialEq=true)
+parcelable RemotelyProvisionedKey {
+ /**
+ * The remotely-provisioned key that may be used to sign attestations. The
+ * format of this key is opaque, and need only be understood by the
+ * IRemotelyProvisionedComponent that generated it.
+ *
+ * Any private key material contained within this blob must be encrypted.
+ *
+ * @see IRemotelyProvisionedComponent
+ */
+ byte[] keyBlob;
+
+ /**
+ * Sequence of DER-encoded X.509 certificates that make up the attestation
+ * key's certificate chain. This is the binary encoding for a chain that is
+ * supported by Java's CertificateFactory.generateCertificates API.
+ */
+ byte[] encodedCertChain;
+}
diff --git a/core/java/android/service/autofill/augmented/AugmentedAutofillService.java b/core/java/android/service/autofill/augmented/AugmentedAutofillService.java
index d9a310f7a6a3..745f36db15e8 100644
--- a/core/java/android/service/autofill/augmented/AugmentedAutofillService.java
+++ b/core/java/android/service/autofill/augmented/AugmentedAutofillService.java
@@ -332,7 +332,6 @@ public abstract class AugmentedAutofillService extends Service {
}
@Override
- /** @hide */
protected final void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.print("Service component: "); pw.println(
ComponentName.flattenToShortString(mServiceComponentName));
diff --git a/core/java/android/service/controls/ControlsProviderService.java b/core/java/android/service/controls/ControlsProviderService.java
index d2a4ae282061..9396a888ec13 100644
--- a/core/java/android/service/controls/ControlsProviderService.java
+++ b/core/java/android/service/controls/ControlsProviderService.java
@@ -69,6 +69,18 @@ public abstract class ControlsProviderService extends Service {
"android.service.controls.META_DATA_PANEL_ACTIVITY";
/**
+ * Boolean extra containing the value of
+ * {@link android.provider.Settings.Secure#LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS}.
+ *
+ * This is passed with the intent when the panel specified by {@link #META_DATA_PANEL_ACTIVITY}
+ * is launched.
+ *
+ * @hide
+ */
+ public static final String EXTRA_LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS =
+ "android.service.controls.EXTRA_LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS";
+
+ /**
* @hide
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index e821af1ab313..d113a3cc0c4b 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -267,6 +267,8 @@ public abstract class NotificationListenerService extends Service {
* will be restored via NotificationListeners#notifyPostedLocked()
*/
public static final int REASON_LOCKDOWN = 23;
+ // If adding a new notification cancellation reason, you must also add handling for it in
+ // NotificationCancelledEvent.fromCancelReason.
/**
* @hide
diff --git a/core/java/android/util/SparseArray.java b/core/java/android/util/SparseArray.java
index 05c8617294da..cc83decc7b21 100644
--- a/core/java/android/util/SparseArray.java
+++ b/core/java/android/util/SparseArray.java
@@ -525,9 +525,10 @@ public class SparseArray<E> implements Cloneable {
return false;
}
+ // size() calls above took care about gc() compaction.
for (int index = 0; index < size; index++) {
- int key = keyAt(index);
- if (!Objects.equals(valueAt(index), other.get(key))) {
+ if (mKeys[index] != other.mKeys[index]
+ || !Objects.equals(mValues[index], other.mValues[index])) {
return false;
}
}
@@ -545,10 +546,11 @@ public class SparseArray<E> implements Cloneable {
public int contentHashCode() {
int hash = 0;
int size = size();
+ // size() call above took care about gc() compaction.
for (int index = 0; index < size; index++) {
- int key = keyAt(index);
- E value = valueAt(index);
- hash = 31 * hash + Objects.hashCode(key);
+ int key = mKeys[index];
+ E value = (E) mValues[index];
+ hash = 31 * hash + key;
hash = 31 * hash + Objects.hashCode(value);
}
return hash;
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 274585892b61..a42d3eb9a2d2 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -1942,13 +1942,16 @@ public final class Display {
private final float mRefreshRate;
@NonNull
private final float[] mAlternativeRefreshRates;
+ @NonNull
+ @HdrCapabilities.HdrType
+ private final int[] mSupportedHdrTypes;
/**
* @hide
*/
@TestApi
public Mode(int width, int height, float refreshRate) {
- this(INVALID_MODE_ID, width, height, refreshRate, new float[0]);
+ this(INVALID_MODE_ID, width, height, refreshRate, new float[0], new int[0]);
}
/**
@@ -1956,14 +1959,14 @@ public final class Display {
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public Mode(int modeId, int width, int height, float refreshRate) {
- this(modeId, width, height, refreshRate, new float[0]);
+ this(modeId, width, height, refreshRate, new float[0], new int[0]);
}
/**
* @hide
*/
public Mode(int modeId, int width, int height, float refreshRate,
- float[] alternativeRefreshRates) {
+ float[] alternativeRefreshRates, @HdrCapabilities.HdrType int[] supportedHdrTypes) {
mModeId = modeId;
mWidth = width;
mHeight = height;
@@ -1971,6 +1974,8 @@ public final class Display {
mAlternativeRefreshRates =
Arrays.copyOf(alternativeRefreshRates, alternativeRefreshRates.length);
Arrays.sort(mAlternativeRefreshRates);
+ mSupportedHdrTypes = Arrays.copyOf(supportedHdrTypes, supportedHdrTypes.length);
+ Arrays.sort(mSupportedHdrTypes);
}
/**
@@ -2045,6 +2050,15 @@ public final class Display {
}
/**
+ * Returns the supported {@link HdrCapabilities} HDR_TYPE_* for this specific mode
+ */
+ @NonNull
+ @HdrCapabilities.HdrType
+ public int[] getSupportedHdrTypes() {
+ return mSupportedHdrTypes;
+ }
+
+ /**
* Returns {@code true} if this mode matches the given parameters.
*
* @hide
@@ -2118,7 +2132,8 @@ public final class Display {
}
Mode that = (Mode) other;
return mModeId == that.mModeId && matches(that.mWidth, that.mHeight, that.mRefreshRate)
- && Arrays.equals(mAlternativeRefreshRates, that.mAlternativeRefreshRates);
+ && Arrays.equals(mAlternativeRefreshRates, that.mAlternativeRefreshRates)
+ && Arrays.equals(mSupportedHdrTypes, that.mSupportedHdrTypes);
}
@Override
@@ -2129,6 +2144,7 @@ public final class Display {
hash = hash * 17 + mHeight;
hash = hash * 17 + Float.floatToIntBits(mRefreshRate);
hash = hash * 17 + Arrays.hashCode(mAlternativeRefreshRates);
+ hash = hash * 17 + Arrays.hashCode(mSupportedHdrTypes);
return hash;
}
@@ -2141,6 +2157,8 @@ public final class Display {
.append(", fps=").append(mRefreshRate)
.append(", alternativeRefreshRates=")
.append(Arrays.toString(mAlternativeRefreshRates))
+ .append(", supportedHdrTypes=")
+ .append(Arrays.toString(mSupportedHdrTypes))
.append("}")
.toString();
}
@@ -2151,7 +2169,8 @@ public final class Display {
}
private Mode(Parcel in) {
- this(in.readInt(), in.readInt(), in.readInt(), in.readFloat(), in.createFloatArray());
+ this(in.readInt(), in.readInt(), in.readInt(), in.readFloat(), in.createFloatArray(),
+ in.createIntArray());
}
@Override
@@ -2161,6 +2180,7 @@ public final class Display {
out.writeInt(mHeight);
out.writeFloat(mRefreshRate);
out.writeFloatArray(mAlternativeRefreshRates);
+ out.writeIntArray(mSupportedHdrTypes);
}
@SuppressWarnings("hiding")
@@ -2326,6 +2346,9 @@ public final class Display {
/**
* Gets the supported HDR types of this display.
* Returns empty array if HDR is not supported by the display.
+ *
+ * @deprecated use {@link Display#getMode()}
+ * and {@link Mode#getSupportedHdrTypes()} instead
*/
public @HdrType int[] getSupportedHdrTypes() {
return mSupportedHdrTypes;
diff --git a/core/java/android/view/DisplayEventReceiver.java b/core/java/android/view/DisplayEventReceiver.java
index 169c8c51bb53..ce7606a0cf70 100644
--- a/core/java/android/view/DisplayEventReceiver.java
+++ b/core/java/android/view/DisplayEventReceiver.java
@@ -227,8 +227,10 @@ public abstract class DisplayEventReceiver {
* timebase.
* @param physicalDisplayId Stable display ID that uniquely describes a (display, port) pair.
* @param modeId The new mode Id
+ * @param renderPeriod The render frame period, which is a multiple of the mode's vsync period
*/
- public void onModeChanged(long timestampNanos, long physicalDisplayId, int modeId) {
+ public void onModeChanged(long timestampNanos, long physicalDisplayId, int modeId,
+ long renderPeriod) {
}
/**
@@ -303,8 +305,9 @@ public abstract class DisplayEventReceiver {
// Called from native code.
@SuppressWarnings("unused")
- private void dispatchModeChanged(long timestampNanos, long physicalDisplayId, int modeId) {
- onModeChanged(timestampNanos, physicalDisplayId, modeId);
+ private void dispatchModeChanged(long timestampNanos, long physicalDisplayId, int modeId,
+ long renderPeriod) {
+ onModeChanged(timestampNanos, physicalDisplayId, modeId, renderPeriod);
}
// Called from native code.
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index 138017c5f0ec..85cb517f6369 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -180,6 +180,16 @@ public final class DisplayInfo implements Parcelable {
public int modeId;
/**
+ * The render frame rate this display is scheduled at, which is a divisor of the active mode
+ * refresh rate. This is the rate SurfaceFlinger would consume frames and would be observable
+ * by applications via the cadence of {@link android.view.Choreographer} callbacks and
+ * by backpressure when submitting buffers as fast as possible.
+ * Apps can call {@link android.view.Display#getRefreshRate} to query this value.
+ *
+ */
+ public float renderFrameRate;
+
+ /**
* The default display mode.
*/
public int defaultModeId;
@@ -376,6 +386,7 @@ public final class DisplayInfo implements Parcelable {
&& Objects.equals(displayCutout, other.displayCutout)
&& rotation == other.rotation
&& modeId == other.modeId
+ && renderFrameRate == other.renderFrameRate
&& defaultModeId == other.defaultModeId
&& Arrays.equals(supportedModes, other.supportedModes)
&& colorMode == other.colorMode
@@ -428,6 +439,7 @@ public final class DisplayInfo implements Parcelable {
displayCutout = other.displayCutout;
rotation = other.rotation;
modeId = other.modeId;
+ renderFrameRate = other.renderFrameRate;
defaultModeId = other.defaultModeId;
supportedModes = Arrays.copyOf(other.supportedModes, other.supportedModes.length);
colorMode = other.colorMode;
@@ -475,6 +487,7 @@ public final class DisplayInfo implements Parcelable {
displayCutout = DisplayCutout.ParcelableWrapper.readCutoutFromParcel(source);
rotation = source.readInt();
modeId = source.readInt();
+ renderFrameRate = source.readFloat();
defaultModeId = source.readInt();
int nModes = source.readInt();
supportedModes = new Display.Mode[nModes];
@@ -535,6 +548,7 @@ public final class DisplayInfo implements Parcelable {
DisplayCutout.ParcelableWrapper.writeCutoutToParcel(displayCutout, dest, flags);
dest.writeInt(rotation);
dest.writeInt(modeId);
+ dest.writeFloat(renderFrameRate);
dest.writeInt(defaultModeId);
dest.writeInt(supportedModes.length);
for (int i = 0; i < supportedModes.length; i++) {
@@ -764,6 +778,7 @@ public final class DisplayInfo implements Parcelable {
sb.append(presentationDeadlineNanos);
sb.append(", mode ");
sb.append(modeId);
+ sb.append(renderFrameRate);
sb.append(", defaultMode ");
sb.append(defaultModeId);
sb.append(", modes ");
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 0743ccb37f51..6d9f99f76958 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -53,7 +53,6 @@ import android.view.IWindowSessionCallback;
import android.view.KeyEvent;
import android.view.InputEvent;
import android.view.InsetsState;
-import android.view.InsetsVisibilities;
import android.view.MagnificationSpec;
import android.view.MotionEvent;
import android.view.InputChannel;
diff --git a/core/java/android/view/InsetsFrameProvider.java b/core/java/android/view/InsetsFrameProvider.java
index da54da16585d..58ee59d1b36b 100644
--- a/core/java/android/view/InsetsFrameProvider.java
+++ b/core/java/android/view/InsetsFrameProvider.java
@@ -31,6 +31,10 @@ import java.util.Objects;
*
* The insets frame will by default as the window frame size. If the providers are set, the
* calculation result based on the source size will be used as the insets frame.
+ *
+ * The InsetsFrameProvider should be self-contained. Nothing describing the window itself, such as
+ * contentInsets, visibleInsets, etc. won't affect the insets providing to other windows when this
+ * is set.
* @hide
*/
public class InsetsFrameProvider implements Parcelable {
diff --git a/core/java/android/view/InsetsVisibilities.java b/core/java/android/view/InsetsVisibilities.java
deleted file mode 100644
index 7d259fb91634..000000000000
--- a/core/java/android/view/InsetsVisibilities.java
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view;
-
-import android.annotation.NonNull;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import java.util.Arrays;
-import java.util.StringJoiner;
-
-/**
- * A collection of visibilities of insets. This is used for carrying the requested visibilities.
- * @hide
- */
-public class InsetsVisibilities implements Parcelable {
-
- private static final int UNSPECIFIED = 0;
- private static final int VISIBLE = 1;
- private static final int INVISIBLE = -1;
-
- private final int[] mVisibilities = new int[InsetsState.SIZE];
-
- public InsetsVisibilities() {
- }
-
- public InsetsVisibilities(InsetsVisibilities other) {
- set(other);
- }
-
- public InsetsVisibilities(Parcel in) {
- in.readIntArray(mVisibilities);
- }
-
- /**
- * Copies from another {@link InsetsVisibilities}.
- *
- * @param other an instance of {@link InsetsVisibilities}.
- */
- public void set(InsetsVisibilities other) {
- System.arraycopy(other.mVisibilities, InsetsState.FIRST_TYPE, mVisibilities,
- InsetsState.FIRST_TYPE, InsetsState.SIZE);
- }
-
- /**
- * Sets a visibility to a type.
- *
- * @param type The {@link @InsetsState.InternalInsetsType}.
- * @param visible {@code true} represents visible; {@code false} represents invisible.
- */
- public void setVisibility(@InsetsState.InternalInsetsType int type, boolean visible) {
- mVisibilities[type] = visible ? VISIBLE : INVISIBLE;
- }
-
- /**
- * Returns the specified insets visibility of the type. If it has never been specified,
- * this returns the default visibility.
- *
- * @param type The {@link @InsetsState.InternalInsetsType}.
- * @return The specified visibility or the default one if it is not specified.
- */
- public boolean getVisibility(@InsetsState.InternalInsetsType int type) {
- final int visibility = mVisibilities[type];
- return visibility == UNSPECIFIED
- ? InsetsState.getDefaultVisibility(type)
- : visibility == VISIBLE;
- }
-
- @Override
- public String toString() {
- StringJoiner joiner = new StringJoiner(", ");
- for (int type = InsetsState.FIRST_TYPE; type <= InsetsState.LAST_TYPE; type++) {
- final int visibility = mVisibilities[type];
- if (visibility != UNSPECIFIED) {
- joiner.add(InsetsState.typeToString(type) + ": "
- + (visibility == VISIBLE ? "visible" : "invisible"));
- }
- }
- return joiner.toString();
- }
-
- @Override
- public int hashCode() {
- return Arrays.hashCode(mVisibilities);
- }
-
- @Override
- public boolean equals(Object other) {
- if (!(other instanceof InsetsVisibilities)) {
- return false;
- }
- return Arrays.equals(mVisibilities, ((InsetsVisibilities) other).mVisibilities);
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeIntArray(mVisibilities);
- }
-
- public void readFromParcel(@NonNull Parcel in) {
- in.readIntArray(mVisibilities);
- }
-
- public static final @NonNull Creator<InsetsVisibilities> CREATOR =
- new Creator<InsetsVisibilities>() {
-
- public InsetsVisibilities createFromParcel(Parcel in) {
- return new InsetsVisibilities(in);
- }
-
- public InsetsVisibilities[] newArray(int size) {
- return new InsetsVisibilities[size];
- }
- };
-}
diff --git a/core/java/android/view/KeyCharacterMap.java b/core/java/android/view/KeyCharacterMap.java
index a6f88a7410c2..ea5d9a684ba4 100644
--- a/core/java/android/view/KeyCharacterMap.java
+++ b/core/java/android/view/KeyCharacterMap.java
@@ -177,6 +177,8 @@ public class KeyCharacterMap implements Parcelable {
private static final int ACCENT_UMLAUT = '\u00A8';
private static final int ACCENT_VERTICAL_LINE_ABOVE = '\u02C8';
private static final int ACCENT_VERTICAL_LINE_BELOW = '\u02CC';
+ private static final int ACCENT_APOSTROPHE = '\'';
+ private static final int ACCENT_QUOTATION_MARK = '"';
/* Legacy dead key display characters used in previous versions of the API.
* We still support these characters by mapping them to their non-legacy version. */
@@ -204,8 +206,6 @@ public class KeyCharacterMap implements Parcelable {
addCombining('\u030A', ACCENT_RING_ABOVE);
addCombining('\u030B', ACCENT_DOUBLE_ACUTE);
addCombining('\u030C', ACCENT_CARON);
- addCombining('\u030D', ACCENT_VERTICAL_LINE_ABOVE);
- //addCombining('\u030E', ACCENT_DOUBLE_VERTICAL_LINE_ABOVE);
//addCombining('\u030F', ACCENT_DOUBLE_GRAVE);
//addCombining('\u0310', ACCENT_CANDRABINDU);
//addCombining('\u0311', ACCENT_INVERTED_BREVE);
@@ -229,11 +229,17 @@ public class KeyCharacterMap implements Parcelable {
sCombiningToAccent.append('\u0340', ACCENT_GRAVE);
sCombiningToAccent.append('\u0341', ACCENT_ACUTE);
sCombiningToAccent.append('\u0343', ACCENT_COMMA_ABOVE);
+ sCombiningToAccent.append('\u030D', ACCENT_APOSTROPHE);
+ sCombiningToAccent.append('\u030E', ACCENT_QUOTATION_MARK);
// One-way legacy mappings to preserve compatibility with older applications.
sAccentToCombining.append(ACCENT_GRAVE_LEGACY, '\u0300');
sAccentToCombining.append(ACCENT_CIRCUMFLEX_LEGACY, '\u0302');
sAccentToCombining.append(ACCENT_TILDE_LEGACY, '\u0303');
+
+ // One-way mappings to use the preferred accent
+ sAccentToCombining.append(ACCENT_APOSTROPHE, '\u0301');
+ sAccentToCombining.append(ACCENT_QUOTATION_MARK, '\u0308');
}
private static void addCombining(int combining, int accent) {
diff --git a/core/java/android/view/RemoteAnimationTarget.java b/core/java/android/view/RemoteAnimationTarget.java
index 5e17551b2ccf..8d8ddb930c23 100644
--- a/core/java/android/view/RemoteAnimationTarget.java
+++ b/core/java/android/view/RemoteAnimationTarget.java
@@ -35,6 +35,7 @@ import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
import android.annotation.ColorInt;
import android.annotation.IntDef;
+import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.TaskInfo;
import android.app.WindowConfiguration;
@@ -175,10 +176,16 @@ public class RemoteAnimationTarget implements Parcelable {
public final Rect screenSpaceBounds;
/**
- * The starting bounds of the source container in screen space coordinates. This is {@code null}
- * if the animation target isn't MODE_CHANGING. Since this is the starting bounds, it's size
- * should be equivalent to the size of the starting thumbnail. Note that sourceContainerBounds
- * is the end bounds of a change transition.
+ * The starting bounds of the source container in screen space coordinates.
+ * For {@link #MODE_OPENING}, this will be equivalent to {@link #screenSpaceBounds}.
+ * For {@link #MODE_CLOSING}, this will be equivalent to {@link #screenSpaceBounds} unless the
+ * closing container is also resizing. For example, when ActivityEmbedding split pair becomes
+ * stacked, the container on the back will be resized to fullscreen, but will also be covered
+ * (closing) by the container in the front.
+ * For {@link #MODE_CHANGING}, since this is the starting bounds, its size should be equivalent
+ * to the bounds of the starting thumbnail.
+ *
+ * Note that {@link #screenSpaceBounds} is the end bounds of a transition.
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public final Rect startBounds;
@@ -247,7 +254,8 @@ public class RemoteAnimationTarget implements Parcelable {
Rect clipRect, Rect contentInsets, int prefixOrderIndex, Point position,
Rect localBounds, Rect screenSpaceBounds,
WindowConfiguration windowConfig, boolean isNotInRecents,
- SurfaceControl startLeash, Rect startBounds, ActivityManager.RunningTaskInfo taskInfo,
+ SurfaceControl startLeash, @Nullable Rect startBounds,
+ ActivityManager.RunningTaskInfo taskInfo,
boolean allowEnterPip) {
this(taskId, mode, leash, isTranslucent, clipRect, contentInsets, prefixOrderIndex,
position, localBounds, screenSpaceBounds, windowConfig, isNotInRecents, startLeash,
@@ -258,7 +266,7 @@ public class RemoteAnimationTarget implements Parcelable {
Rect clipRect, Rect contentInsets, int prefixOrderIndex, Point position,
Rect localBounds, Rect screenSpaceBounds,
WindowConfiguration windowConfig, boolean isNotInRecents,
- SurfaceControl startLeash, Rect startBounds,
+ SurfaceControl startLeash, @Nullable Rect startBounds,
ActivityManager.RunningTaskInfo taskInfo, boolean allowEnterPip,
@WindowManager.LayoutParams.WindowType int windowType) {
this.mode = mode;
@@ -275,10 +283,13 @@ public class RemoteAnimationTarget implements Parcelable {
this.windowConfiguration = windowConfig;
this.isNotInRecents = isNotInRecents;
this.startLeash = startLeash;
- this.startBounds = startBounds == null ? null : new Rect(startBounds);
this.taskInfo = taskInfo;
this.allowEnterPip = allowEnterPip;
this.windowType = windowType;
+ // Same as screenSpaceBounds if the window is not resizing.
+ this.startBounds = startBounds == null
+ ? new Rect(screenSpaceBounds)
+ : new Rect(startBounds);
}
public RemoteAnimationTarget(Parcel in) {
@@ -399,9 +410,7 @@ public class RemoteAnimationTarget implements Parcelable {
if (startLeash != null) {
startLeash.dumpDebug(proto, START_LEASH);
}
- if (startBounds != null) {
- startBounds.dumpDebug(proto, START_BOUNDS);
- }
+ startBounds.dumpDebug(proto, START_BOUNDS);
proto.end(token);
}
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index e1ca0f139113..277b90cc04bc 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -187,8 +187,8 @@ public final class SurfaceControl implements Parcelable {
int L, int T, int R, int B);
private static native void nativeSetDisplaySize(long transactionObj, IBinder displayToken,
int width, int height);
- private static native StaticDisplayInfo nativeGetStaticDisplayInfo(IBinder displayToken);
- private static native DynamicDisplayInfo nativeGetDynamicDisplayInfo(IBinder displayToken);
+ private static native StaticDisplayInfo nativeGetStaticDisplayInfo(long displayId);
+ private static native DynamicDisplayInfo nativeGetDynamicDisplayInfo(long displayId);
private static native DisplayedContentSamplingAttributes
nativeGetDisplayedContentSamplingAttributes(IBinder displayToken);
private static native boolean nativeSetDisplayedContentSamplingEnabled(IBinder displayToken,
@@ -1504,6 +1504,7 @@ public final class SurfaceControl implements Parcelable {
public static final class DynamicDisplayInfo {
public DisplayMode[] supportedDisplayModes;
public int activeDisplayModeId;
+ public float renderFrameRate;
public int[] supportedColorModes;
public int activeColorMode;
@@ -1520,6 +1521,7 @@ public final class SurfaceControl implements Parcelable {
return "DynamicDisplayInfo{"
+ "supportedDisplayModes=" + Arrays.toString(supportedDisplayModes)
+ ", activeDisplayModeId=" + activeDisplayModeId
+ + ", renderFrameRate=" + renderFrameRate
+ ", supportedColorModes=" + Arrays.toString(supportedColorModes)
+ ", activeColorMode=" + activeColorMode
+ ", hdrCapabilities=" + hdrCapabilities
@@ -1535,6 +1537,7 @@ public final class SurfaceControl implements Parcelable {
DynamicDisplayInfo that = (DynamicDisplayInfo) o;
return Arrays.equals(supportedDisplayModes, that.supportedDisplayModes)
&& activeDisplayModeId == that.activeDisplayModeId
+ && renderFrameRate == that.renderFrameRate
&& Arrays.equals(supportedColorModes, that.supportedColorModes)
&& activeColorMode == that.activeColorMode
&& Objects.equals(hdrCapabilities, that.hdrCapabilities)
@@ -1544,7 +1547,7 @@ public final class SurfaceControl implements Parcelable {
@Override
public int hashCode() {
return Objects.hash(Arrays.hashCode(supportedDisplayModes), activeDisplayModeId,
- activeColorMode, hdrCapabilities);
+ renderFrameRate, activeColorMode, hdrCapabilities);
}
}
@@ -1563,6 +1566,7 @@ public final class SurfaceControl implements Parcelable {
public float refreshRate;
public long appVsyncOffsetNanos;
public long presentationDeadlineNanos;
+ public int[] supportedHdrTypes;
/**
* The config group ID this config is associated to.
@@ -1582,6 +1586,7 @@ public final class SurfaceControl implements Parcelable {
+ ", refreshRate=" + refreshRate
+ ", appVsyncOffsetNanos=" + appVsyncOffsetNanos
+ ", presentationDeadlineNanos=" + presentationDeadlineNanos
+ + ", supportedHdrTypes=" + Arrays.toString(supportedHdrTypes)
+ ", group=" + group + "}";
}
@@ -1598,13 +1603,14 @@ public final class SurfaceControl implements Parcelable {
&& Float.compare(that.refreshRate, refreshRate) == 0
&& appVsyncOffsetNanos == that.appVsyncOffsetNanos
&& presentationDeadlineNanos == that.presentationDeadlineNanos
+ && Arrays.equals(supportedHdrTypes, that.supportedHdrTypes)
&& group == that.group;
}
@Override
public int hashCode() {
return Objects.hash(id, width, height, xDpi, yDpi, refreshRate, appVsyncOffsetNanos,
- presentationDeadlineNanos, group);
+ presentationDeadlineNanos, group, Arrays.hashCode(supportedHdrTypes));
}
}
@@ -1621,21 +1627,15 @@ public final class SurfaceControl implements Parcelable {
/**
* @hide
*/
- public static StaticDisplayInfo getStaticDisplayInfo(IBinder displayToken) {
- if (displayToken == null) {
- throw new IllegalArgumentException("displayToken must not be null");
- }
- return nativeGetStaticDisplayInfo(displayToken);
+ public static StaticDisplayInfo getStaticDisplayInfo(long displayId) {
+ return nativeGetStaticDisplayInfo(displayId);
}
/**
* @hide
*/
- public static DynamicDisplayInfo getDynamicDisplayInfo(IBinder displayToken) {
- if (displayToken == null) {
- throw new IllegalArgumentException("displayToken must not be null");
- }
- return nativeGetDynamicDisplayInfo(displayToken);
+ public static DynamicDisplayInfo getDynamicDisplayInfo(long displayId) {
+ return nativeGetDynamicDisplayInfo(displayId);
}
/**
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 09a9d46ba257..727011c5a94f 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -77,6 +77,7 @@ 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_APPLICATION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
+import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE;
import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL;
import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
@@ -470,16 +471,6 @@ public final class ViewRootImpl implements ViewParent,
private boolean mAppVisibilityChanged;
int mOrigWindowType = -1;
- /** Whether the window had focus during the most recent traversal. */
- boolean mHadWindowFocus;
-
- /**
- * Whether the window lost focus during a previous traversal and has not
- * yet gained it back. Used to determine whether a WINDOW_STATE_CHANGE
- * accessibility events should be sent during traversal.
- */
- boolean mLostWindowFocus;
-
// Set to true if the owner of this window is in the stopped state,
// so the window should no longer be active.
@UnsupportedAppUsage
@@ -505,6 +496,13 @@ public final class ViewRootImpl implements ViewParent,
Region mTouchableRegion;
Region mPreviousTouchableRegion;
+ private int mMeasuredWidth;
+ private int mMeasuredHeight;
+
+ // This indicates that we've already known the window size but without measuring the views.
+ // If this is true, we must measure the views before laying out them.
+ private boolean mViewMeasureDeferred;
+
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
int mWidth;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -2593,7 +2591,8 @@ public final class ViewRootImpl implements ViewParent,
}
private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp,
- final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) {
+ final Resources res, final int desiredWindowWidth, final int desiredWindowHeight,
+ boolean forRootSizeOnly) {
int childWidthMeasureSpec;
int childHeightMeasureSpec;
boolean windowSizeMayChange = false;
@@ -2649,7 +2648,15 @@ public final class ViewRootImpl implements ViewParent,
lp.privateFlags);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height,
lp.privateFlags);
- performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
+ if (!forRootSizeOnly || !setMeasuredRootSizeFromSpec(
+ childWidthMeasureSpec, childHeightMeasureSpec)) {
+ performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
+ } else {
+ // We already know how big the window should be before measuring the views.
+ // We can measure the views before laying out them. This is to avoid unnecessary
+ // measure.
+ mViewMeasureDeferred = true;
+ }
if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) {
windowSizeMayChange = true;
}
@@ -2665,6 +2672,25 @@ public final class ViewRootImpl implements ViewParent,
}
/**
+ * Sets the measured root size for requesting the window frame.
+ *
+ * @param widthMeasureSpec contains the size and the mode of the width.
+ * @param heightMeasureSpec contains the size and the mode of the height.
+ * @return {@code true} if we actually set the measured size; {@code false} otherwise.
+ */
+ private boolean setMeasuredRootSizeFromSpec(int widthMeasureSpec, int heightMeasureSpec) {
+ final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+ final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+ if (widthMode != MeasureSpec.EXACTLY || heightMode != MeasureSpec.EXACTLY) {
+ // We don't know the exact size. We need to measure the hierarchy to know that.
+ return false;
+ }
+ mMeasuredWidth = MeasureSpec.getSize(widthMeasureSpec);
+ mMeasuredHeight = MeasureSpec.getSize(heightMeasureSpec);
+ return true;
+ }
+
+ /**
* Modifies the input matrix such that it maps view-local coordinates to
* on-screen coordinates.
*
@@ -2751,6 +2777,14 @@ public final class ViewRootImpl implements ViewParent,
|| lp.type == TYPE_VOLUME_OVERLAY;
}
+ /**
+ * @return {@code true} if we should reduce unnecessary measure for the window.
+ * TODO(b/260382739): Apply this to all windows.
+ */
+ private static boolean shouldOptimizeMeasure(final WindowManager.LayoutParams lp) {
+ return lp.type == TYPE_NOTIFICATION_SHADE;
+ }
+
private Rect getWindowBoundsInsetSystemBars() {
final Rect bounds = new Rect(
mContext.getResources().getConfiguration().windowConfiguration.getBounds());
@@ -2801,6 +2835,7 @@ public final class ViewRootImpl implements ViewParent,
mAppVisibilityChanged = false;
final boolean viewUserVisibilityChanged = !mFirst &&
((mViewVisibility == View.VISIBLE) != (viewVisibility == View.VISIBLE));
+ final boolean shouldOptimizeMeasure = shouldOptimizeMeasure(lp);
WindowManager.LayoutParams params = null;
CompatibilityInfo compatibilityInfo =
@@ -2922,7 +2957,7 @@ public final class ViewRootImpl implements ViewParent,
// Ask host how big it wants to be
windowSizeMayChange |= measureHierarchy(host, lp, mView.getContext().getResources(),
- desiredWindowWidth, desiredWindowHeight);
+ desiredWindowWidth, desiredWindowHeight, shouldOptimizeMeasure);
}
if (collectViewAttributes()) {
@@ -2962,8 +2997,8 @@ public final class ViewRootImpl implements ViewParent,
// we don't need to go through two layout passes when things
// change due to fitting system windows, which can happen a lot.
windowSizeMayChange |= measureHierarchy(host, lp,
- mView.getContext().getResources(),
- desiredWindowWidth, desiredWindowHeight);
+ mView.getContext().getResources(), desiredWindowWidth, desiredWindowHeight,
+ shouldOptimizeMeasure);
}
}
@@ -3383,6 +3418,13 @@ public final class ViewRootImpl implements ViewParent,
maybeHandleWindowMove(frame);
}
+ if (mViewMeasureDeferred) {
+ // It's time to measure the views since we are going to layout them.
+ performMeasure(
+ MeasureSpec.makeMeasureSpec(frame.width(), MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(frame.height(), MeasureSpec.EXACTLY));
+ }
+
if (!mRelayoutRequested && mCheckIfCanDraw) {
// We had a sync previously, but we didn't call IWindowSession#relayout in this
// traversal. So we don't know if the sync is complete that we can continue to draw.
@@ -3578,20 +3620,8 @@ public final class ViewRootImpl implements ViewParent,
}
final boolean changedVisibility = (viewVisibilityChanged || mFirst) && isViewVisible;
- final boolean hasWindowFocus = mAttachInfo.mHasWindowFocus && isViewVisible;
- final boolean regainedFocus = hasWindowFocus && mLostWindowFocus;
- if (regainedFocus) {
- mLostWindowFocus = false;
- } else if (!hasWindowFocus && mHadWindowFocus) {
- mLostWindowFocus = true;
- }
-
- if (changedVisibility || regainedFocus) {
- // Toasts are presented as notifications - don't present them as windows as well
- boolean isToast = mWindowAttributes.type == TYPE_TOAST;
- if (!isToast) {
- host.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
- }
+ if (changedVisibility) {
+ maybeFireAccessibilityWindowStateChangedEvent();
}
mFirst = false;
@@ -3599,8 +3629,8 @@ public final class ViewRootImpl implements ViewParent,
mNewSurfaceNeeded = false;
mActivityRelaunched = false;
mViewVisibility = viewVisibility;
- mHadWindowFocus = hasWindowFocus;
+ final boolean hasWindowFocus = mAttachInfo.mHasWindowFocus && isViewVisible;
mImeFocusController.onTraversal(hasWindowFocus, mWindowAttributes);
if ((relayoutResult & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
@@ -3843,6 +3873,8 @@ public final class ViewRootImpl implements ViewParent,
~WindowManager.LayoutParams
.SOFT_INPUT_IS_FORWARD_NAVIGATION;
+ maybeFireAccessibilityWindowStateChangedEvent();
+
// Refocusing a window that has a focused view should fire a
// focus event for the view since the global focused view changed.
fireAccessibilityFocusEventIfHasFocusedNode();
@@ -3870,6 +3902,14 @@ public final class ViewRootImpl implements ViewParent,
ensureTouchModeLocally(inTouchMode);
}
+ private void maybeFireAccessibilityWindowStateChangedEvent() {
+ // Toasts are presented as notifications - don't present them as windows as well.
+ boolean isToast = mWindowAttributes != null && (mWindowAttributes.type == TYPE_TOAST);
+ if (!isToast && mView != null) {
+ mView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+ }
+ }
+
private void fireAccessibilityFocusEventIfHasFocusedNode() {
if (!AccessibilityManager.getInstance(mContext).isEnabled()) {
return;
@@ -3973,6 +4013,9 @@ public final class ViewRootImpl implements ViewParent,
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
+ mMeasuredWidth = mView.getMeasuredWidth();
+ mMeasuredHeight = mView.getMeasuredHeight();
+ mViewMeasureDeferred = false;
}
/**
@@ -4068,7 +4111,7 @@ public final class ViewRootImpl implements ViewParent,
view.requestLayout();
}
measureHierarchy(host, lp, mView.getContext().getResources(),
- desiredWindowWidth, desiredWindowHeight);
+ desiredWindowWidth, desiredWindowHeight, false /* forRootSizeOnly */);
mInLayout = true;
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
@@ -8167,8 +8210,8 @@ public final class ViewRootImpl implements ViewParent,
final WindowConfiguration winConfigFromWm =
mLastReportedMergedConfiguration.getGlobalConfiguration().windowConfiguration;
final WindowConfiguration winConfig = getCompatWindowConfiguration();
- final int measuredWidth = mView.getMeasuredWidth();
- final int measuredHeight = mView.getMeasuredHeight();
+ final int measuredWidth = mMeasuredWidth;
+ final int measuredHeight = mMeasuredHeight;
final boolean relayoutAsync;
if (LOCAL_LAYOUT
&& (mViewFrameInfo.flags & FrameInfo.FLAG_WINDOW_VISIBILITY_CHANGED) == 0
diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java
index 8de15c1d0ce6..fd55d8d55bc9 100644
--- a/core/java/android/view/WindowInsets.java
+++ b/core/java/android/view/WindowInsets.java
@@ -1493,37 +1493,37 @@ public final class WindowInsets {
public static String toString(@InsetsType int types) {
StringBuilder result = new StringBuilder();
if ((types & STATUS_BARS) != 0) {
- result.append("statusBars |");
+ result.append("statusBars ");
}
if ((types & NAVIGATION_BARS) != 0) {
- result.append("navigationBars |");
+ result.append("navigationBars ");
}
if ((types & CAPTION_BAR) != 0) {
- result.append("captionBar |");
+ result.append("captionBar ");
}
if ((types & IME) != 0) {
- result.append("ime |");
+ result.append("ime ");
}
if ((types & SYSTEM_GESTURES) != 0) {
- result.append("systemGestures |");
+ result.append("systemGestures ");
}
if ((types & MANDATORY_SYSTEM_GESTURES) != 0) {
- result.append("mandatorySystemGestures |");
+ result.append("mandatorySystemGestures ");
}
if ((types & TAPPABLE_ELEMENT) != 0) {
- result.append("tappableElement |");
+ result.append("tappableElement ");
}
if ((types & DISPLAY_CUTOUT) != 0) {
- result.append("displayCutout |");
+ result.append("displayCutout ");
}
if ((types & WINDOW_DECOR) != 0) {
- result.append("windowDecor |");
+ result.append("windowDecor ");
}
if ((types & SYSTEM_OVERLAYS) != 0) {
- result.append("systemOverlays |");
+ result.append("systemOverlays ");
}
if (result.length() > 0) {
- result.delete(result.length() - 2, result.length());
+ result.delete(result.length() - 1, result.length());
}
return result.toString();
}
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index c067955e1114..b91199dc0ae5 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -67,6 +67,7 @@ import com.android.internal.util.Preconditions;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -881,7 +882,7 @@ public class AccessibilityNodeInfo implements Parcelable {
private long mTraversalBefore = UNDEFINED_NODE_ID;
private long mTraversalAfter = UNDEFINED_NODE_ID;
- private int mMinMillisBetweenContentChanges;
+ private long mMinDurationBetweenContentChanges = 0;
private int mBooleanProperties;
private final Rect mBoundsInParent = new Rect();
@@ -1797,18 +1798,21 @@ public class AccessibilityNodeInfo implements Parcelable {
* </p>
*
* @see AccessibilityEvent#getContentChangeTypes for all content change types.
- * @param minMillisBetweenContentChanges the minimum duration between content change events.
+ * @param minDurationBetweenContentChanges the minimum duration between content change events.
+ * Negative duration would be treated as zero.
*/
- public void setMinMillisBetweenContentChanges(int minMillisBetweenContentChanges) {
+ public void setMinDurationBetweenContentChanges(
+ @NonNull Duration minDurationBetweenContentChanges) {
enforceNotSealed();
- mMinMillisBetweenContentChanges = minMillisBetweenContentChanges;
+ mMinDurationBetweenContentChanges = minDurationBetweenContentChanges.toMillis();
}
/**
* Gets the minimum time duration between two content change events.
*/
- public int getMinMillisBetweenContentChanges() {
- return mMinMillisBetweenContentChanges;
+ @NonNull
+ public Duration getMinDurationBetweenContentChanges() {
+ return Duration.ofMillis(mMinDurationBetweenContentChanges);
}
/**
@@ -4013,8 +4017,8 @@ public class AccessibilityNodeInfo implements Parcelable {
fieldIndex++;
if (mTraversalAfter != DEFAULT.mTraversalAfter) nonDefaultFields |= bitAt(fieldIndex);
fieldIndex++;
- if (mMinMillisBetweenContentChanges
- != DEFAULT.mMinMillisBetweenContentChanges) {
+ if (mMinDurationBetweenContentChanges
+ != DEFAULT.mMinDurationBetweenContentChanges) {
nonDefaultFields |= bitAt(fieldIndex);
}
fieldIndex++;
@@ -4148,7 +4152,7 @@ public class AccessibilityNodeInfo implements Parcelable {
if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeLong(mTraversalBefore);
if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeLong(mTraversalAfter);
if (isBitSet(nonDefaultFields, fieldIndex++)) {
- parcel.writeInt(mMinMillisBetweenContentChanges);
+ parcel.writeLong(mMinDurationBetweenContentChanges);
}
if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeInt(mConnectionId);
@@ -4305,7 +4309,7 @@ public class AccessibilityNodeInfo implements Parcelable {
mLabeledById = other.mLabeledById;
mTraversalBefore = other.mTraversalBefore;
mTraversalAfter = other.mTraversalAfter;
- mMinMillisBetweenContentChanges = other.mMinMillisBetweenContentChanges;
+ mMinDurationBetweenContentChanges = other.mMinDurationBetweenContentChanges;
mWindowId = other.mWindowId;
mConnectionId = other.mConnectionId;
mUniqueId = other.mUniqueId;
@@ -4410,7 +4414,7 @@ public class AccessibilityNodeInfo implements Parcelable {
if (isBitSet(nonDefaultFields, fieldIndex++)) mTraversalBefore = parcel.readLong();
if (isBitSet(nonDefaultFields, fieldIndex++)) mTraversalAfter = parcel.readLong();
if (isBitSet(nonDefaultFields, fieldIndex++)) {
- mMinMillisBetweenContentChanges = parcel.readInt();
+ mMinDurationBetweenContentChanges = parcel.readLong();
}
if (isBitSet(nonDefaultFields, fieldIndex++)) mConnectionId = parcel.readInt();
@@ -4760,8 +4764,8 @@ public class AccessibilityNodeInfo implements Parcelable {
builder.append("; mParentNodeId: 0x").append(Long.toHexString(mParentNodeId));
builder.append("; traversalBefore: 0x").append(Long.toHexString(mTraversalBefore));
builder.append("; traversalAfter: 0x").append(Long.toHexString(mTraversalAfter));
- builder.append("; minMillisBetweenContentChanges: ")
- .append(mMinMillisBetweenContentChanges);
+ builder.append("; minDurationBetweenContentChanges: ")
+ .append(mMinDurationBetweenContentChanges);
int granularities = mMovementGranularities;
builder.append("; MovementGranularities: [");
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index a25194804987..364c7c8e1fb9 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -27,6 +27,7 @@ import android.view.accessibility.IAccessibilityInteractionConnection;
import android.view.accessibility.IAccessibilityManagerClient;
import android.view.accessibility.AccessibilityWindowAttributes;
import android.view.accessibility.IWindowMagnificationConnection;
+import android.view.InputEvent;
import android.view.IWindow;
/**
@@ -114,4 +115,7 @@ interface IAccessibilityManager {
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY)")
boolean unregisterProxyForDisplay(int displayId);
+
+ // Used by UiAutomation for tests on the InputFilter
+ void injectInputEventToInputFilter(in InputEvent event);
}
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 558d96089bb7..0a3ea8a9f356 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -2082,7 +2082,8 @@ public class Editor {
}
if (selectionHighlight != null && selectionStart == selectionEnd
- && mDrawableForCursor != null) {
+ && mDrawableForCursor != null
+ && !mTextView.hasGesturePreviewHighlight()) {
drawCursor(canvas, cursorOffsetVertical);
// Rely on the drawable entirely, do not draw the cursor line.
// Has to be done after the IMM related code above which relies on the highlight.
@@ -2714,7 +2715,7 @@ public class Editor {
unregisterOnBackInvokedCallback();
}
- private void stopTextActionModeWithPreservingSelection() {
+ void stopTextActionModeWithPreservingSelection() {
if (mTextActionMode != null) {
mRestartActionModeOnNextRefresh = true;
}
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 475a8491de4d..b9b928e4c2f9 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -68,6 +68,7 @@ import android.content.res.XmlResourceParser;
import android.graphics.BaseCanvas;
import android.graphics.BlendMode;
import android.graphics.Canvas;
+import android.graphics.Color;
import android.graphics.Insets;
import android.graphics.Matrix;
import android.graphics.Paint;
@@ -87,6 +88,7 @@ import android.os.AsyncTask;
import android.os.Build;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
+import android.os.CancellationSignal;
import android.os.Handler;
import android.os.LocaleList;
import android.os.Parcel;
@@ -148,6 +150,7 @@ import android.text.style.SuggestionSpan;
import android.text.style.URLSpan;
import android.text.style.UpdateAppearance;
import android.text.util.Linkify;
+import android.util.ArraySet;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.FeatureFlagUtils;
@@ -199,6 +202,7 @@ import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.InsertGesture;
import android.view.inputmethod.JoinOrSplitGesture;
+import android.view.inputmethod.PreviewableHandwritingGesture;
import android.view.inputmethod.RemoveSpaceGesture;
import android.view.inputmethod.SelectGesture;
import android.view.inputmethod.SelectRangeGesture;
@@ -222,6 +226,7 @@ import android.widget.RemoteViews.RemoteView;
import com.android.internal.accessibility.util.AccessibilityUtils;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.graphics.ColorUtils;
import com.android.internal.inputmethod.EditableInputConnection;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
@@ -242,6 +247,7 @@ import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
+import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
@@ -931,6 +937,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private List<Path> mHighlightPaths;
private List<Paint> mHighlightPaints;
private Highlights mHighlights;
+ private int mGesturePreviewHighlightStart = -1;
+ private int mGesturePreviewHighlightEnd = -1;
+ private Paint mGesturePreviewHighlightPaint;
private final List<Path> mPathRecyclePool = new ArrayList<>();
private boolean mHighlightPathsBogus = true;
@@ -6167,6 +6176,59 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
/**
+ * Highlights the text range (from inclusive start offset to exclusive end offset) to show what
+ * will be selected by the ongoing select handwriting gesture. While the gesture preview
+ * highlight is shown, the selection or cursor is hidden. If the text or selection is changed,
+ * the gesture preview highlight will be cleared.
+ */
+ private void setSelectGesturePreviewHighlight(int start, int end) {
+ // Selection preview highlight color is the same as selection highlight color.
+ setGesturePreviewHighlight(start, end, mHighlightColor);
+ }
+
+ /**
+ * Highlights the text range (from inclusive start offset to exclusive end offset) to show what
+ * will be deleted by the ongoing delete handwriting gesture. While the gesture preview
+ * highlight is shown, the selection or cursor is hidden. If the text or selection is changed,
+ * the gesture preview highlight will be cleared.
+ */
+ private void setDeleteGesturePreviewHighlight(int start, int end) {
+ // Deletion preview highlight color is 20% opacity of the default text color.
+ int color = mTextColor.getDefaultColor();
+ color = ColorUtils.setAlphaComponent(color, (int) (0.2f * Color.alpha(color)));
+ setGesturePreviewHighlight(start, end, color);
+ }
+
+ private void setGesturePreviewHighlight(int start, int end, int color) {
+ mGesturePreviewHighlightStart = start;
+ mGesturePreviewHighlightEnd = end;
+ if (mGesturePreviewHighlightPaint == null) {
+ mGesturePreviewHighlightPaint = new Paint();
+ mGesturePreviewHighlightPaint.setStyle(Paint.Style.FILL);
+ }
+ mGesturePreviewHighlightPaint.setColor(color);
+
+ if (mEditor != null) {
+ mEditor.hideCursorAndSpanControllers();
+ mEditor.stopTextActionModeWithPreservingSelection();
+ }
+
+ mHighlightPathsBogus = true;
+ invalidate();
+ }
+
+ private void clearGesturePreviewHighlight() {
+ mGesturePreviewHighlightStart = -1;
+ mGesturePreviewHighlightEnd = -1;
+ mHighlightPathsBogus = true;
+ invalidate();
+ }
+
+ boolean hasGesturePreviewHighlight() {
+ return mGesturePreviewHighlightStart > 0;
+ }
+
+ /**
* Convenience method to append the specified text to the TextView's
* display buffer, upgrading it to {@link android.widget.TextView.BufferType#EDITABLE}
* if it was not already editable.
@@ -8300,6 +8362,22 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
}
+
+ if (hasGesturePreviewHighlight()) {
+ final Path path;
+ if (mPathRecyclePool.isEmpty()) {
+ path = new Path();
+ } else {
+ path = mPathRecyclePool.get(mPathRecyclePool.size() - 1);
+ mPathRecyclePool.remove(mPathRecyclePool.size() - 1);
+ path.reset();
+ }
+ mLayout.getSelectionPath(
+ mGesturePreviewHighlightStart, mGesturePreviewHighlightEnd, path);
+ mHighlightPaths.add(path);
+ mHighlightPaints.add(mGesturePreviewHighlightPaint);
+ }
+
mHighlightPathsBogus = false;
}
@@ -8503,7 +8581,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
final int cursorOffsetVertical = voffsetCursor - voffsetText;
maybeUpdateHighlightPaths();
- Path highlight = getUpdatedHighlightPath();
+ // If there is a gesture preview highlight, then the selection or cursor is not drawn.
+ Path highlight = hasGesturePreviewHighlight() ? null : getUpdatedHighlightPath();
if (mEditor != null) {
mEditor.onDraw(canvas, layout, mHighlightPaths, mHighlightPaints, highlight,
mHighlightPaint, cursorOffsetVertical);
@@ -9200,6 +9279,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
gestures.add(RemoveSpaceGesture.class);
gestures.add(JoinOrSplitGesture.class);
outAttrs.setSupportedHandwritingGestures(gestures);
+
+ Set<Class<? extends PreviewableHandwritingGesture>> previews = new ArraySet<>();
+ previews.add(SelectGesture.class);
+ previews.add(SelectRangeGesture.class);
+ previews.add(DeleteGesture.class);
+ previews.add(DeleteRangeGesture.class);
+ outAttrs.setSupportedHandwritingGesturePreviews(previews);
+
return ic;
}
}
@@ -9411,83 +9498,130 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
/** @hide */
+ public boolean previewHandwritingGesture(
+ @NonNull PreviewableHandwritingGesture gesture,
+ @Nullable CancellationSignal cancellationSignal) {
+ if (gesture instanceof SelectGesture) {
+ performHandwritingSelectGesture((SelectGesture) gesture, /* isPreview= */ true);
+ } else if (gesture instanceof SelectRangeGesture) {
+ performHandwritingSelectRangeGesture(
+ (SelectRangeGesture) gesture, /* isPreview= */ true);
+ } else if (gesture instanceof DeleteGesture) {
+ performHandwritingDeleteGesture((DeleteGesture) gesture, /* isPreview= */ true);
+ } else if (gesture instanceof DeleteRangeGesture) {
+ performHandwritingDeleteRangeGesture(
+ (DeleteRangeGesture) gesture, /* isPreview= */ true);
+ } else {
+ return false;
+ }
+ if (cancellationSignal != null) {
+ cancellationSignal.setOnCancelListener(this::clearGesturePreviewHighlight);
+ }
+ return true;
+ }
+
+ /** @hide */
public int performHandwritingSelectGesture(@NonNull SelectGesture gesture) {
+ return performHandwritingSelectGesture(gesture, /* isPreview= */ false);
+ }
+
+ private int performHandwritingSelectGesture(@NonNull SelectGesture gesture, boolean isPreview) {
int[] range = getRangeForRect(
convertFromScreenToContentCoordinates(gesture.getSelectionArea()),
gesture.getGranularity());
if (range == null) {
- return handleGestureFailure(gesture);
+ return handleGestureFailure(gesture, isPreview);
+ }
+ return performHandwritingSelectGesture(range, isPreview);
+ }
+
+ private int performHandwritingSelectGesture(int[] range, boolean isPreview) {
+ if (isPreview) {
+ setSelectGesturePreviewHighlight(range[0], range[1]);
+ } else {
+ Selection.setSelection(getEditableText(), range[0], range[1]);
+ mEditor.startSelectionActionModeAsync(/* adjustSelection= */ false);
}
- Selection.setSelection(getEditableText(), range[0], range[1]);
- mEditor.startSelectionActionModeAsync(/* adjustSelection= */ false);
return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS;
}
/** @hide */
public int performHandwritingSelectRangeGesture(@NonNull SelectRangeGesture gesture) {
+ return performHandwritingSelectRangeGesture(gesture, /* isPreview= */ false);
+ }
+
+ private int performHandwritingSelectRangeGesture(
+ @NonNull SelectRangeGesture gesture, boolean isPreview) {
int[] startRange = getRangeForRect(
convertFromScreenToContentCoordinates(gesture.getSelectionStartArea()),
gesture.getGranularity());
if (startRange == null) {
- return handleGestureFailure(gesture);
+ return handleGestureFailure(gesture, isPreview);
}
int[] endRange = getRangeForRect(
convertFromScreenToContentCoordinates(gesture.getSelectionEndArea()),
gesture.getGranularity());
if (endRange == null) {
- return handleGestureFailure(gesture);
+ return handleGestureFailure(gesture, isPreview);
}
int[] range = new int[] {
Math.min(startRange[0], endRange[0]), Math.max(startRange[1], endRange[1])
};
- Selection.setSelection(getEditableText(), range[0], range[1]);
- mEditor.startSelectionActionModeAsync(/* adjustSelection= */ false);
- return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS;
+ return performHandwritingSelectGesture(range, isPreview);
}
/** @hide */
public int performHandwritingDeleteGesture(@NonNull DeleteGesture gesture) {
+ return performHandwritingDeleteGesture(gesture, /* isPreview= */ false);
+ }
+
+ private int performHandwritingDeleteGesture(@NonNull DeleteGesture gesture, boolean isPreview) {
int[] range = getRangeForRect(
convertFromScreenToContentCoordinates(gesture.getDeletionArea()),
gesture.getGranularity());
if (range == null) {
- return handleGestureFailure(gesture);
+ return handleGestureFailure(gesture, isPreview);
}
+ return performHandwritingDeleteGesture(range, gesture.getGranularity(), isPreview);
+ }
- if (gesture.getGranularity() == HandwritingGesture.GRANULARITY_WORD) {
- range = adjustHandwritingDeleteGestureRange(range);
- }
+ private int performHandwritingDeleteGesture(int[] range, int granularity, boolean isPreview) {
+ if (isPreview) {
+ setDeleteGesturePreviewHighlight(range[0], range[1]);
+ } else {
+ if (granularity == HandwritingGesture.GRANULARITY_WORD) {
+ range = adjustHandwritingDeleteGestureRange(range);
+ }
- getEditableText().delete(range[0], range[1]);
- Selection.setSelection(getEditableText(), range[0]);
+ getEditableText().delete(range[0], range[1]);
+ Selection.setSelection(getEditableText(), range[0]);
+ }
return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS;
}
/** @hide */
public int performHandwritingDeleteRangeGesture(@NonNull DeleteRangeGesture gesture) {
+ return performHandwritingDeleteRangeGesture(gesture, /* isPreview= */ false);
+ }
+
+ private int performHandwritingDeleteRangeGesture(
+ @NonNull DeleteRangeGesture gesture, boolean isPreview) {
int[] startRange = getRangeForRect(
convertFromScreenToContentCoordinates(gesture.getDeletionStartArea()),
gesture.getGranularity());
if (startRange == null) {
- return handleGestureFailure(gesture);
+ return handleGestureFailure(gesture, isPreview);
}
int[] endRange = getRangeForRect(
convertFromScreenToContentCoordinates(gesture.getDeletionEndArea()),
gesture.getGranularity());
if (endRange == null) {
- return handleGestureFailure(gesture);
+ return handleGestureFailure(gesture, isPreview);
}
int[] range = new int[] {
Math.min(startRange[0], endRange[0]), Math.max(startRange[1], endRange[1])
};
-
- if (gesture.getGranularity() == HandwritingGesture.GRANULARITY_WORD) {
- range = adjustHandwritingDeleteGestureRange(range);
- }
-
- getEditableText().delete(range[0], range[1]);
- Selection.setSelection(getEditableText(), range[0]);
- return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS;
+ return performHandwritingDeleteGesture(range, gesture.getGranularity(), isPreview);
}
private int[] adjustHandwritingDeleteGestureRange(int[] range) {
@@ -9665,7 +9799,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
private int handleGestureFailure(HandwritingGesture gesture) {
- if (!TextUtils.isEmpty(gesture.getFallbackText())) {
+ return handleGestureFailure(gesture, /* isPreview= */ false);
+ }
+
+ private int handleGestureFailure(HandwritingGesture gesture, boolean isPreview) {
+ clearGesturePreviewHighlight();
+ if (!isPreview && !TextUtils.isEmpty(gesture.getFallbackText())) {
getEditableText()
.replace(getSelectionStart(), getSelectionEnd(), gesture.getFallbackText());
return InputConnection.HANDWRITING_GESTURE_RESULT_FALLBACK;
@@ -11699,6 +11838,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
resetErrorChangedFlag();
sendOnTextChanged(buffer, start, before, after);
onTextChanged(buffer, start, before, after);
+
+ clearGesturePreviewHighlight();
}
/**
@@ -11737,6 +11878,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
if (selChanged) {
+ clearGesturePreviewHighlight();
mHighlightPathBogus = true;
if (mEditor != null && !isFocused()) mEditor.mSelectionMoved = true;
diff --git a/core/java/android/window/BackEvent.java b/core/java/android/window/BackEvent.java
index 85b288113b45..40c0feee7c94 100644
--- a/core/java/android/window/BackEvent.java
+++ b/core/java/android/window/BackEvent.java
@@ -16,29 +16,24 @@
package android.window;
+import android.annotation.FloatRange;
import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.view.RemoteAnimationTarget;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
- * Represents an event that is sent out by the system during back navigation gesture.
- * Holds information about the touch event, swipe direction and overall progress of the gesture
- * interaction.
- *
- * @hide
+ * Object used to report back gesture progress.
+ * Holds information about the touch event, swipe direction and the animation progress that
+ * predictive back animations should seek to.
*/
-public class BackEvent implements Parcelable {
+public final class BackEvent {
/** Indicates that the edge swipe starts from the left edge of the screen */
public static final int EDGE_LEFT = 0;
/** Indicates that the edge swipe starts from the right edge of the screen */
public static final int EDGE_RIGHT = 1;
+ /** @hide */
@IntDef({
EDGE_LEFT,
EDGE_RIGHT,
@@ -52,78 +47,52 @@ public class BackEvent implements Parcelable {
@SwipeEdge
private final int mSwipeEdge;
- @Nullable
- private final RemoteAnimationTarget mDepartingAnimationTarget;
/**
- * Creates a new {@link BackEvent} instance.
+ * Creates a new {@link BackMotionEvent} instance.
*
* @param touchX Absolute X location of the touch point of this event.
* @param touchY Absolute Y location of the touch point of this event.
* @param progress Value between 0 and 1 on how far along the back gesture is.
* @param swipeEdge Indicates which edge the swipe starts from.
- * @param departingAnimationTarget The remote animation target of the departing
- * application window.
*/
- public BackEvent(float touchX, float touchY, float progress, @SwipeEdge int swipeEdge,
- @Nullable RemoteAnimationTarget departingAnimationTarget) {
+ public BackEvent(float touchX, float touchY, float progress, @SwipeEdge int swipeEdge) {
mTouchX = touchX;
mTouchY = touchY;
mProgress = progress;
mSwipeEdge = swipeEdge;
- mDepartingAnimationTarget = departingAnimationTarget;
- }
-
- private BackEvent(@NonNull Parcel in) {
- mTouchX = in.readFloat();
- mTouchY = in.readFloat();
- mProgress = in.readFloat();
- mSwipeEdge = in.readInt();
- mDepartingAnimationTarget = in.readTypedObject(RemoteAnimationTarget.CREATOR);
- }
-
- public static final Creator<BackEvent> CREATOR = new Creator<BackEvent>() {
- @Override
- public BackEvent createFromParcel(Parcel in) {
- return new BackEvent(in);
- }
-
- @Override
- public BackEvent[] newArray(int size) {
- return new BackEvent[size];
- }
- };
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeFloat(mTouchX);
- dest.writeFloat(mTouchY);
- dest.writeFloat(mProgress);
- dest.writeInt(mSwipeEdge);
- dest.writeTypedObject(mDepartingAnimationTarget, flags);
}
/**
- * Returns a value between 0 and 1 on how far along the back gesture is.
+ * Returns a value between 0 and 1 on how far along the back gesture is. This value is
+ * driven by the horizontal location of the touch point, and should be used as the fraction to
+ * seek the predictive back animation with. Specifically,
+ * <ol>
+ * <li>The progress is 0 when the touch is at the starting edge of the screen (left or right),
+ * and animation should seek to its start state.
+ * <li>The progress is approximately 1 when the touch is at the opposite side of the screen,
+ * and animation should seek to its end state. Exact end value may vary depending on
+ * screen size.
+ * </ol>
+ * In-between locations are linearly interpolated based on horizontal distance from the starting
+ * edge and smooth clamped to 1 when the distance exceeds a system-wide threshold.
*/
+ @FloatRange(from = 0, to = 1)
public float getProgress() {
return mProgress;
}
/**
- * Returns the absolute X location of the touch point.
+ * Returns the absolute X location of the touch point, or NaN if the event is from
+ * a button press.
*/
public float getTouchX() {
return mTouchX;
}
/**
- * Returns the absolute Y location of the touch point.
+ * Returns the absolute Y location of the touch point, or NaN if the event is from
+ * a button press.
*/
public float getTouchY() {
return mTouchY;
@@ -132,20 +101,11 @@ public class BackEvent implements Parcelable {
/**
* Returns the screen edge that the swipe starts from.
*/
+ @SwipeEdge
public int getSwipeEdge() {
return mSwipeEdge;
}
- /**
- * Returns the {@link RemoteAnimationTarget} of the top departing application window,
- * or {@code null} if the top window should not be moved for the current type of back
- * destination.
- */
- @Nullable
- public RemoteAnimationTarget getDepartingAnimationTarget() {
- return mDepartingAnimationTarget;
- }
-
@Override
public String toString() {
return "BackEvent{"
diff --git a/core/java/android/window/BackEvent.aidl b/core/java/android/window/BackMotionEvent.aidl
index 821f1fa9830f..7c675c35c073 100644
--- a/core/java/android/window/BackEvent.aidl
+++ b/core/java/android/window/BackMotionEvent.aidl
@@ -19,4 +19,4 @@ package android.window;
/**
* @hide
*/
-parcelable BackEvent;
+parcelable BackMotionEvent;
diff --git a/core/java/android/window/BackMotionEvent.java b/core/java/android/window/BackMotionEvent.java
new file mode 100644
index 000000000000..8012a1c26bac
--- /dev/null
+++ b/core/java/android/window/BackMotionEvent.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.window;
+
+import android.annotation.FloatRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.RemoteAnimationTarget;
+
+/**
+ * Object used to report back gesture progress. Holds information about a {@link BackEvent} plus
+ * any {@link RemoteAnimationTarget} the gesture manipulates.
+ *
+ * @see BackEvent
+ * @hide
+ */
+public final class BackMotionEvent implements Parcelable {
+ private final float mTouchX;
+ private final float mTouchY;
+ private final float mProgress;
+
+ @BackEvent.SwipeEdge
+ private final int mSwipeEdge;
+ @Nullable
+ private final RemoteAnimationTarget mDepartingAnimationTarget;
+
+ /**
+ * Creates a new {@link BackMotionEvent} instance.
+ *
+ * @param touchX Absolute X location of the touch point of this event.
+ * @param touchY Absolute Y location of the touch point of this event.
+ * @param progress Value between 0 and 1 on how far along the back gesture is.
+ * @param swipeEdge Indicates which edge the swipe starts from.
+ * @param departingAnimationTarget The remote animation target of the departing
+ * application window.
+ */
+ public BackMotionEvent(float touchX, float touchY, float progress,
+ @BackEvent.SwipeEdge int swipeEdge,
+ @Nullable RemoteAnimationTarget departingAnimationTarget) {
+ mTouchX = touchX;
+ mTouchY = touchY;
+ mProgress = progress;
+ mSwipeEdge = swipeEdge;
+ mDepartingAnimationTarget = departingAnimationTarget;
+ }
+
+ private BackMotionEvent(@NonNull Parcel in) {
+ mTouchX = in.readFloat();
+ mTouchY = in.readFloat();
+ mProgress = in.readFloat();
+ mSwipeEdge = in.readInt();
+ mDepartingAnimationTarget = in.readTypedObject(RemoteAnimationTarget.CREATOR);
+ }
+
+ @NonNull
+ public static final Creator<BackMotionEvent> CREATOR = new Creator<BackMotionEvent>() {
+ @Override
+ public BackMotionEvent createFromParcel(Parcel in) {
+ return new BackMotionEvent(in);
+ }
+
+ @Override
+ public BackMotionEvent[] newArray(int size) {
+ return new BackMotionEvent[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeFloat(mTouchX);
+ dest.writeFloat(mTouchY);
+ dest.writeFloat(mProgress);
+ dest.writeInt(mSwipeEdge);
+ dest.writeTypedObject(mDepartingAnimationTarget, flags);
+ }
+
+ /**
+ * Returns the progress of a {@link BackEvent}.
+ *
+ * @see BackEvent#getProgress()
+ */
+ @FloatRange(from = 0, to = 1)
+ public float getProgress() {
+ return mProgress;
+ }
+
+ /**
+ * Returns the absolute X location of the touch point.
+ */
+ public float getTouchX() {
+ return mTouchX;
+ }
+
+ /**
+ * Returns the absolute Y location of the touch point.
+ */
+ public float getTouchY() {
+ return mTouchY;
+ }
+
+ /**
+ * Returns the screen edge that the swipe starts from.
+ */
+ @BackEvent.SwipeEdge
+ public int getSwipeEdge() {
+ return mSwipeEdge;
+ }
+
+ /**
+ * Returns the {@link RemoteAnimationTarget} of the top departing application window,
+ * or {@code null} if the top window should not be moved for the current type of back
+ * destination.
+ */
+ @Nullable
+ public RemoteAnimationTarget getDepartingAnimationTarget() {
+ return mDepartingAnimationTarget;
+ }
+
+ @Override
+ public String toString() {
+ return "BackMotionEvent{"
+ + "mTouchX=" + mTouchX
+ + ", mTouchY=" + mTouchY
+ + ", mProgress=" + mProgress
+ + ", mSwipeEdge" + mSwipeEdge
+ + ", mDepartingAnimationTarget" + mDepartingAnimationTarget
+ + "}";
+ }
+}
diff --git a/core/java/android/window/BackProgressAnimator.java b/core/java/android/window/BackProgressAnimator.java
index 2e3afde1a78a..14a57e047a08 100644
--- a/core/java/android/window/BackProgressAnimator.java
+++ b/core/java/android/window/BackProgressAnimator.java
@@ -40,7 +40,7 @@ public class BackProgressAnimator {
private final SpringAnimation mSpring;
private ProgressCallback mCallback;
private float mProgress = 0;
- private BackEvent mLastBackEvent;
+ private BackMotionEvent mLastBackEvent;
private boolean mStarted = false;
private void setProgress(float progress) {
@@ -82,9 +82,9 @@ public class BackProgressAnimator {
/**
* Sets a new target position for the back progress.
*
- * @param event the {@link BackEvent} containing the latest target progress.
+ * @param event the {@link BackMotionEvent} containing the latest target progress.
*/
- public void onBackProgressed(BackEvent event) {
+ public void onBackProgressed(BackMotionEvent event) {
if (!mStarted) {
return;
}
@@ -98,11 +98,11 @@ public class BackProgressAnimator {
/**
* Starts the back progress animation.
*
- * @param event the {@link BackEvent} that started the gesture.
+ * @param event the {@link BackMotionEvent} that started the gesture.
* @param callback the back callback to invoke for the gesture. It will receive back progress
* dispatches as the progress animation updates.
*/
- public void onBackStarted(BackEvent event, ProgressCallback callback) {
+ public void onBackStarted(BackMotionEvent event, ProgressCallback callback) {
reset();
mLastBackEvent = event;
mCallback = callback;
@@ -132,8 +132,7 @@ public class BackProgressAnimator {
}
mCallback.onProgressUpdate(
new BackEvent(mLastBackEvent.getTouchX(), mLastBackEvent.getTouchY(),
- progress / SCALE_FACTOR, mLastBackEvent.getSwipeEdge(),
- mLastBackEvent.getDepartingAnimationTarget()));
+ progress / SCALE_FACTOR, mLastBackEvent.getSwipeEdge()));
}
}
diff --git a/core/java/android/window/DisplayWindowPolicyController.java b/core/java/android/window/DisplayWindowPolicyController.java
index f55932eb05fd..e027934ccb51 100644
--- a/core/java/android/window/DisplayWindowPolicyController.java
+++ b/core/java/android/window/DisplayWindowPolicyController.java
@@ -128,9 +128,10 @@ public abstract class DisplayWindowPolicyController {
ActivityInfo activityInfo, int windowFlags, int systemWindowFlags);
/**
- * Returns {@code true} if the tasks which is on this virtual display can be showed on Recents.
+ * Returns {@code true} if the tasks which is on this virtual display can be showed in the
+ * host device of the recently launched activities list.
*/
- public abstract boolean canShowTasksInRecents();
+ public abstract boolean canShowTasksInHostDeviceRecents();
/**
* This is called when the top activity of the display is changed.
diff --git a/core/java/android/window/IOnBackInvokedCallback.aidl b/core/java/android/window/IOnBackInvokedCallback.aidl
index 6af8ddda3a62..159c0e8afed0 100644
--- a/core/java/android/window/IOnBackInvokedCallback.aidl
+++ b/core/java/android/window/IOnBackInvokedCallback.aidl
@@ -17,7 +17,7 @@
package android.window;
-import android.window.BackEvent;
+import android.window.BackMotionEvent;
/**
* Interface that wraps a {@link OnBackInvokedCallback} object, to be stored in window manager
@@ -30,18 +30,19 @@ oneway interface IOnBackInvokedCallback {
* Called when a back gesture has been started, or back button has been pressed down.
* Wraps {@link OnBackInvokedCallback#onBackStarted(BackEvent)}.
*
- * @param backEvent The {@link BackEvent} containing information about the touch or button press.
+ * @param backMotionEvent The {@link BackMotionEvent} containing information about the touch
+ * or button press.
*/
- void onBackStarted(in BackEvent backEvent);
+ void onBackStarted(in BackMotionEvent backMotionEvent);
/**
* Called on back gesture progress.
* Wraps {@link OnBackInvokedCallback#onBackProgressed(BackEvent)}.
*
- * @param backEvent The {@link BackEvent} containing information about the latest touch point
- * and the progress that the back animation should seek to.
+ * @param backMotionEvent The {@link BackMotionEvent} containing information about the latest
+ * touch point and the progress that the back animation should seek to.
*/
- void onBackProgressed(in BackEvent backEvent);
+ void onBackProgressed(in BackMotionEvent backMotionEvent);
/**
* Called when a back gesture or back button press has been cancelled.
diff --git a/core/java/android/window/OnBackAnimationCallback.java b/core/java/android/window/OnBackAnimationCallback.java
index c05809bca4a5..9119e71cc655 100644
--- a/core/java/android/window/OnBackAnimationCallback.java
+++ b/core/java/android/window/OnBackAnimationCallback.java
@@ -18,6 +18,8 @@ import android.annotation.NonNull;
import android.app.Activity;
import android.app.Dialog;
import android.view.View;
+import android.view.Window;
+
/**
* Interface for applications to register back animation callbacks along their custom back
* handling.
@@ -25,24 +27,29 @@ import android.view.View;
* This allows the client to customize various back behaviors by overriding the corresponding
* callback methods.
* <p>
- * Callback instances can be added to and removed from {@link OnBackInvokedDispatcher}, held
- * by classes that implement {@link OnBackInvokedDispatcherOwner} (such as {@link Activity},
- * {@link Dialog} and {@link View}).
+ * Callback instances can be added to and removed from {@link OnBackInvokedDispatcher}, which
+ * is held at window level and accessible through {@link Activity#getOnBackInvokedDispatcher()},
+ * {@link Dialog#getOnBackInvokedDispatcher()}, {@link Window#getOnBackInvokedDispatcher()}
+ * and {@link View#findOnBackInvokedDispatcher()}.
* <p>
* When back is triggered, callbacks on the in-focus window are invoked in reverse order in which
* they are added within the same priority. Between different priorities, callbacks with higher
* priority are invoked first.
* <p>
* @see OnBackInvokedCallback
- * @hide
*/
public interface OnBackAnimationCallback extends OnBackInvokedCallback {
/**
* Called when a back gesture has been started, or back button has been pressed down.
+ *
+ * @param backEvent The {@link BackEvent} containing information about the touch or
+ * button press.
+ * @see BackEvent
*/
- default void onBackStarted() { }
+ default void onBackStarted(@NonNull BackEvent backEvent) {}
+
/**
- * Called on back gesture progress.
+ * Called when a back gesture progresses.
*
* @param backEvent An {@link BackEvent} object describing the progress event.
*
diff --git a/core/java/android/window/OnBackInvokedCallback.java b/core/java/android/window/OnBackInvokedCallback.java
index 62c41bfb0681..6beaad339b1d 100644
--- a/core/java/android/window/OnBackInvokedCallback.java
+++ b/core/java/android/window/OnBackInvokedCallback.java
@@ -16,9 +16,9 @@
package android.window;
-import android.annotation.NonNull;
import android.app.Activity;
import android.app.Dialog;
+import android.view.View;
import android.view.Window;
/**
@@ -26,7 +26,8 @@ import android.view.Window;
* <p>
* Callback instances can be added to and removed from {@link OnBackInvokedDispatcher}, which
* is held at window level and accessible through {@link Activity#getOnBackInvokedDispatcher()},
- * {@link Dialog#getOnBackInvokedDispatcher()} and {@link Window#getOnBackInvokedDispatcher()}.
+ * {@link Dialog#getOnBackInvokedDispatcher()}, {@link Window#getOnBackInvokedDispatcher()}
+ * and {@link View#findOnBackInvokedDispatcher()}.
* <p>
* When back is triggered, callbacks on the in-focus window are invoked in reverse order in which
* they are added within the same priority. Between different priorities, callbacks with higher
@@ -35,6 +36,9 @@ import android.view.Window;
* This replaces {@link Activity#onBackPressed()}, {@link Dialog#onBackPressed()} and
* {@link android.view.KeyEvent#KEYCODE_BACK}
* <p>
+ * If you want to customize back animation behaviors, in addition to handling back invocations,
+ * register its subclass instances {@link OnBackAnimationCallback} instead.
+ * <p>
* @see OnBackInvokedDispatcher#registerOnBackInvokedCallback(int, OnBackInvokedCallback)
* registerOnBackInvokedCallback(priority, OnBackInvokedCallback)
* to specify callback priority.
@@ -42,35 +46,8 @@ import android.view.Window;
@SuppressWarnings("deprecation")
public interface OnBackInvokedCallback {
/**
- * Called when a back gesture has been started, or back button has been pressed down.
- *
- * @param backEvent The {@link BackEvent} containing information about the touch or
- * button press.
- *
- * @hide
- */
- default void onBackStarted(@NonNull BackEvent backEvent) {}
-
- /**
- * Called when a back gesture has been progressed.
- *
- * @param backEvent The {@link BackEvent} containing information about the latest touch point
- * and the progress that the back animation should seek to.
- *
- * @hide
- */
- default void onBackProgressed(@NonNull BackEvent backEvent) {}
-
- /**
* Called when a back gesture has been completed and committed, or back button pressed
* has been released and committed.
*/
void onBackInvoked();
-
- /**
- * Called when a back gesture or button press has been cancelled.
- *
- * @hide
- */
- default void onBackCancelled() {}
}
diff --git a/core/java/android/window/SnapshotDrawerUtils.java b/core/java/android/window/SnapshotDrawerUtils.java
new file mode 100644
index 000000000000..1a58fd556609
--- /dev/null
+++ b/core/java/android/window/SnapshotDrawerUtils.java
@@ -0,0 +1,508 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.window;
+
+import static android.graphics.Color.WHITE;
+import static android.graphics.Color.alpha;
+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;
+import static android.view.WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
+import static android.view.WindowManager.LayoutParams.FLAG_SCALED;
+import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
+import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY;
+import static android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
+import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION;
+import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
+import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_USE_BLAST;
+
+import static com.android.internal.policy.DecorView.NAVIGATION_BAR_COLOR_VIEW_ATTRIBUTES;
+import static com.android.internal.policy.DecorView.STATUS_BAR_COLOR_VIEW_ATTRIBUTES;
+import static com.android.internal.policy.DecorView.getNavigationBarRect;
+
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.ActivityThread;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.GraphicBuffer;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.hardware.HardwareBuffer;
+import android.os.IBinder;
+import android.util.Log;
+import android.view.InsetsState;
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
+import android.view.ViewGroup;
+import android.view.WindowInsets;
+import android.view.WindowManager;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.policy.DecorView;
+
+/**
+ * Utils class to help draw a snapshot on a surface.
+ * @hide
+ */
+public class SnapshotDrawerUtils {
+ private static final String TAG = "SnapshotDrawerUtils";
+
+ /**
+ * When creating the starting window, we use the exact same layout flags such that we end up
+ * with a window with the exact same dimensions etc. However, these flags are not used in layout
+ * and might cause other side effects so we exclude them.
+ */
+ static final int FLAG_INHERIT_EXCLUDES = FLAG_NOT_FOCUSABLE
+ | FLAG_NOT_TOUCHABLE
+ | FLAG_NOT_TOUCH_MODAL
+ | FLAG_ALT_FOCUSABLE_IM
+ | FLAG_NOT_FOCUSABLE
+ | FLAG_HARDWARE_ACCELERATED
+ | FLAG_IGNORE_CHEEK_PRESSES
+ | FLAG_LOCAL_FOCUS_MODE
+ | FLAG_SLIPPERY
+ | FLAG_WATCH_OUTSIDE_TOUCH
+ | FLAG_SPLIT_TOUCH
+ | FLAG_SCALED
+ | FLAG_SECURE;
+
+ private static final RectF sTmpSnapshotSize = new RectF();
+ private static final RectF sTmpDstFrame = new RectF();
+
+ private static final Matrix sSnapshotMatrix = new Matrix();
+ private static final float[] sTmpFloat9 = new float[9];
+ private static final Paint sBackgroundPaint = new Paint();
+
+ /**
+ * The internal object to hold the surface and drawing on it.
+ */
+ @VisibleForTesting
+ public static class SnapshotSurface {
+ private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
+ private final SurfaceControl mRootSurface;
+ private final TaskSnapshot mSnapshot;
+ private final CharSequence mTitle;
+
+ private SystemBarBackgroundPainter mSystemBarBackgroundPainter;
+ private final Rect mTaskBounds;
+ private final Rect mFrame = new Rect();
+ private final Rect mSystemBarInsets = new Rect();
+ private boolean mSizeMismatch;
+
+ public SnapshotSurface(SurfaceControl rootSurface, TaskSnapshot snapshot,
+ CharSequence title,
+ Rect taskBounds) {
+ mRootSurface = rootSurface;
+ mSnapshot = snapshot;
+ mTitle = title;
+ mTaskBounds = taskBounds;
+ }
+
+ /**
+ * Initiate system bar painter to draw the system bar background.
+ */
+ void initiateSystemBarPainter(int windowFlags, int windowPrivateFlags,
+ int appearance, ActivityManager.TaskDescription taskDescription,
+ @WindowInsets.Type.InsetsType int requestedVisibleTypes) {
+ mSystemBarBackgroundPainter = new SystemBarBackgroundPainter(windowFlags,
+ windowPrivateFlags, appearance, taskDescription, 1f, requestedVisibleTypes);
+ int backgroundColor = taskDescription.getBackgroundColor();
+ sBackgroundPaint.setColor(backgroundColor != 0 ? backgroundColor : WHITE);
+ }
+
+ /**
+ * Set frame size.
+ */
+ void setFrames(Rect frame, Rect systemBarInsets) {
+ mFrame.set(frame);
+ mSystemBarInsets.set(systemBarInsets);
+ final HardwareBuffer snapshot = mSnapshot.getHardwareBuffer();
+ mSizeMismatch = (mFrame.width() != snapshot.getWidth()
+ || mFrame.height() != snapshot.getHeight());
+ mSystemBarBackgroundPainter.setInsets(systemBarInsets);
+ }
+
+ private void drawSnapshot(boolean releaseAfterDraw) {
+ Log.v(TAG, "Drawing snapshot surface sizeMismatch=" + mSizeMismatch);
+ if (mSizeMismatch) {
+ // The dimensions of the buffer and the window don't match, so attaching the buffer
+ // will fail. Better create a child window with the exact dimensions and fill the
+ // parent window with the background color!
+ drawSizeMismatchSnapshot();
+ } else {
+ drawSizeMatchSnapshot();
+ }
+
+ // In case window manager leaks us, make sure we don't retain the snapshot.
+ if (mSnapshot.getHardwareBuffer() != null) {
+ mSnapshot.getHardwareBuffer().close();
+ }
+ if (releaseAfterDraw) {
+ mRootSurface.release();
+ }
+ }
+
+ private void drawSizeMatchSnapshot() {
+ mTransaction.setBuffer(mRootSurface, mSnapshot.getHardwareBuffer())
+ .setColorSpace(mRootSurface, mSnapshot.getColorSpace())
+ .apply();
+ }
+
+ private void drawSizeMismatchSnapshot() {
+ final HardwareBuffer buffer = mSnapshot.getHardwareBuffer();
+ final SurfaceSession session = new SurfaceSession();
+
+ // We consider nearly matched dimensions as there can be rounding errors and the user
+ // won't notice very minute differences from scaling one dimension more than the other
+ final boolean aspectRatioMismatch = !isAspectRatioMatch(mFrame, mSnapshot);
+
+ // Keep a reference to it such that it doesn't get destroyed when finalized.
+ SurfaceControl childSurfaceControl = new SurfaceControl.Builder(session)
+ .setName(mTitle + " - task-snapshot-surface")
+ .setBLASTLayer()
+ .setFormat(buffer.getFormat())
+ .setParent(mRootSurface)
+ .setCallsite("TaskSnapshotWindow.drawSizeMismatchSnapshot")
+ .build();
+
+ final Rect frame;
+ // We can just show the surface here as it will still be hidden as the parent is
+ // still hidden.
+ mTransaction.show(childSurfaceControl);
+ if (aspectRatioMismatch) {
+ // Clip off ugly navigation bar.
+ final Rect crop = calculateSnapshotCrop();
+ frame = calculateSnapshotFrame(crop);
+ mTransaction.setWindowCrop(childSurfaceControl, crop);
+ mTransaction.setPosition(childSurfaceControl, frame.left, frame.top);
+ sTmpSnapshotSize.set(crop);
+ sTmpDstFrame.set(frame);
+ } else {
+ frame = null;
+ sTmpSnapshotSize.set(0, 0, buffer.getWidth(), buffer.getHeight());
+ sTmpDstFrame.set(mFrame);
+ sTmpDstFrame.offsetTo(0, 0);
+ }
+
+ // Scale the mismatch dimensions to fill the task bounds
+ sSnapshotMatrix.setRectToRect(sTmpSnapshotSize, sTmpDstFrame, Matrix.ScaleToFit.FILL);
+ mTransaction.setMatrix(childSurfaceControl, sSnapshotMatrix, sTmpFloat9);
+ mTransaction.setColorSpace(childSurfaceControl, mSnapshot.getColorSpace());
+ mTransaction.setBuffer(childSurfaceControl, mSnapshot.getHardwareBuffer());
+
+ if (aspectRatioMismatch) {
+ GraphicBuffer background = GraphicBuffer.create(mFrame.width(), mFrame.height(),
+ PixelFormat.RGBA_8888,
+ GraphicBuffer.USAGE_HW_TEXTURE | GraphicBuffer.USAGE_HW_COMPOSER
+ | GraphicBuffer.USAGE_SW_WRITE_RARELY);
+ // TODO: Support this on HardwareBuffer
+ final Canvas c = background.lockCanvas();
+ drawBackgroundAndBars(c, frame);
+ background.unlockCanvasAndPost(c);
+ mTransaction.setBuffer(mRootSurface,
+ HardwareBuffer.createFromGraphicBuffer(background));
+ }
+ mTransaction.apply();
+ childSurfaceControl.release();
+ }
+
+ /**
+ * Calculates the snapshot crop in snapshot coordinate space.
+ *
+ * @return crop rect in snapshot coordinate space.
+ */
+ Rect calculateSnapshotCrop() {
+ final Rect rect = new Rect();
+ final HardwareBuffer snapshot = mSnapshot.getHardwareBuffer();
+ rect.set(0, 0, snapshot.getWidth(), snapshot.getHeight());
+ final Rect insets = mSnapshot.getContentInsets();
+
+ final float scaleX = (float) snapshot.getWidth() / mSnapshot.getTaskSize().x;
+ final float scaleY = (float) snapshot.getHeight() / mSnapshot.getTaskSize().y;
+
+ // Let's remove all system decorations except the status bar, but only if the task is at
+ // the very top of the screen.
+ final boolean isTop = mTaskBounds.top == 0 && mFrame.top == 0;
+ rect.inset((int) (insets.left * scaleX),
+ isTop ? 0 : (int) (insets.top * scaleY),
+ (int) (insets.right * scaleX),
+ (int) (insets.bottom * scaleY));
+ return rect;
+ }
+
+ /**
+ * Calculates the snapshot frame in window coordinate space from crop.
+ *
+ * @param crop rect that is in snapshot coordinate space.
+ */
+ Rect calculateSnapshotFrame(Rect crop) {
+ final HardwareBuffer snapshot = mSnapshot.getHardwareBuffer();
+ final float scaleX = (float) snapshot.getWidth() / mSnapshot.getTaskSize().x;
+ final float scaleY = (float) snapshot.getHeight() / mSnapshot.getTaskSize().y;
+
+ // Rescale the frame from snapshot to window coordinate space
+ final Rect frame = new Rect(0, 0,
+ (int) (crop.width() / scaleX + 0.5f),
+ (int) (crop.height() / scaleY + 0.5f)
+ );
+
+ // However, we also need to make space for the navigation bar on the left side.
+ frame.offset(mSystemBarInsets.left, 0);
+ return frame;
+ }
+
+ /**
+ * Draw status bar and navigation bar background.
+ */
+ void drawBackgroundAndBars(Canvas c, Rect frame) {
+ final int statusBarHeight = mSystemBarBackgroundPainter.getStatusBarColorViewHeight();
+ final boolean fillHorizontally = c.getWidth() > frame.right;
+ final boolean fillVertically = c.getHeight() > frame.bottom;
+ if (fillHorizontally) {
+ c.drawRect(frame.right, alpha(mSystemBarBackgroundPainter.mStatusBarColor) == 0xFF
+ ? statusBarHeight : 0, c.getWidth(), fillVertically
+ ? frame.bottom : c.getHeight(), sBackgroundPaint);
+ }
+ if (fillVertically) {
+ c.drawRect(0, frame.bottom, c.getWidth(), c.getHeight(), sBackgroundPaint);
+ }
+ mSystemBarBackgroundPainter.drawDecors(c, frame);
+ }
+
+ /**
+ * Ask system bar background painter to draw status bar background.
+ *
+ */
+ void drawStatusBarBackground(Canvas c, @Nullable Rect alreadyDrawnFrame) {
+ mSystemBarBackgroundPainter.drawStatusBarBackground(c, alreadyDrawnFrame,
+ mSystemBarBackgroundPainter.getStatusBarColorViewHeight());
+ }
+
+ /**
+ * Ask system bar background painter to draw navigation bar background.
+ *
+ */
+ void drawNavigationBarBackground(Canvas c) {
+ mSystemBarBackgroundPainter.drawNavigationBarBackground(c);
+ }
+ }
+
+ /**
+ * @return true if the aspect ratio match between a frame and a snapshot buffer.
+ */
+ public static boolean isAspectRatioMatch(Rect frame, TaskSnapshot snapshot) {
+ if (frame.isEmpty()) {
+ return false;
+ }
+ final HardwareBuffer buffer = snapshot.getHardwareBuffer();
+ return Math.abs(
+ ((float) buffer.getWidth() / buffer.getHeight())
+ - ((float) frame.width() / frame.height())) <= 0.01f;
+ }
+
+ /**
+ * Help method to draw the snapshot on a surface.
+ */
+ public static void drawSnapshotOnSurface(StartingWindowInfo info, WindowManager.LayoutParams lp,
+ SurfaceControl rootSurface, TaskSnapshot snapshot,
+ Rect configBounds, Rect windowBounds, InsetsState topWindowInsetsState,
+ boolean releaseAfterDraw) {
+ if (windowBounds.isEmpty()) {
+ Log.e(TAG, "Unable to draw snapshot on an empty windowBounds");
+ return;
+ }
+ final SnapshotSurface drawSurface = new SnapshotSurface(
+ rootSurface, snapshot, lp.getTitle(), configBounds);
+
+ final WindowManager.LayoutParams attrs = info.topOpaqueWindowLayoutParams;
+ final ActivityManager.RunningTaskInfo runningTaskInfo = info.taskInfo;
+ final ActivityManager.TaskDescription taskDescription;
+ if (runningTaskInfo.taskDescription != null) {
+ taskDescription = runningTaskInfo.taskDescription;
+ } else {
+ taskDescription = new ActivityManager.TaskDescription();
+ taskDescription.setBackgroundColor(WHITE);
+ }
+ drawSurface.initiateSystemBarPainter(lp.flags, lp.privateFlags,
+ attrs.insetsFlags.appearance, taskDescription, info.requestedVisibleTypes);
+ final Rect systemBarInsets = getSystemBarInsets(windowBounds, topWindowInsetsState);
+ drawSurface.setFrames(windowBounds, systemBarInsets);
+ drawSurface.drawSnapshot(releaseAfterDraw);
+ }
+
+ /**
+ * Help method to create a layout parameters for a window.
+ */
+ public static WindowManager.LayoutParams createLayoutParameters(StartingWindowInfo info,
+ CharSequence title, @WindowManager.LayoutParams.WindowType int windowType,
+ int pixelFormat, IBinder token) {
+ final WindowManager.LayoutParams attrs = info.topOpaqueWindowLayoutParams;
+ final WindowManager.LayoutParams mainWindowParams = info.mainWindowLayoutParams;
+ final InsetsState topWindowInsetsState = info.topOpaqueWindowInsetsState;
+ if (attrs == null || mainWindowParams == null || topWindowInsetsState == null) {
+ Log.w(TAG, "unable to create taskSnapshot surface ");
+ return null;
+ }
+ final WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
+
+ final int appearance = attrs.insetsFlags.appearance;
+ final int windowFlags = attrs.flags;
+ final int windowPrivateFlags = attrs.privateFlags;
+
+ layoutParams.packageName = mainWindowParams.packageName;
+ layoutParams.windowAnimations = mainWindowParams.windowAnimations;
+ layoutParams.dimAmount = mainWindowParams.dimAmount;
+ layoutParams.type = windowType;
+ layoutParams.format = pixelFormat;
+ layoutParams.flags = (windowFlags & ~FLAG_INHERIT_EXCLUDES)
+ | FLAG_NOT_FOCUSABLE
+ | FLAG_NOT_TOUCHABLE;
+ // Setting as trusted overlay to let touches pass through. This is safe because this
+ // window is controlled by the system.
+ layoutParams.privateFlags = (windowPrivateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS)
+ | PRIVATE_FLAG_TRUSTED_OVERLAY | PRIVATE_FLAG_USE_BLAST;
+ layoutParams.token = token;
+ layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
+ layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT;
+ layoutParams.insetsFlags.appearance = appearance;
+ layoutParams.insetsFlags.behavior = attrs.insetsFlags.behavior;
+ layoutParams.layoutInDisplayCutoutMode = attrs.layoutInDisplayCutoutMode;
+ layoutParams.setFitInsetsTypes(attrs.getFitInsetsTypes());
+ layoutParams.setFitInsetsSides(attrs.getFitInsetsSides());
+ layoutParams.setFitInsetsIgnoringVisibility(attrs.isFitInsetsIgnoringVisibility());
+
+ layoutParams.setTitle(title);
+ return layoutParams;
+ }
+
+ static Rect getSystemBarInsets(Rect frame, InsetsState state) {
+ return state.calculateInsets(frame, WindowInsets.Type.systemBars(),
+ false /* ignoreVisibility */).toRect();
+ }
+
+ /**
+ * Helper class to draw the background of the system bars in regions the task snapshot isn't
+ * filling the window.
+ */
+ public static class SystemBarBackgroundPainter {
+ private final Paint mStatusBarPaint = new Paint();
+ private final Paint mNavigationBarPaint = new Paint();
+ private final int mStatusBarColor;
+ private final int mNavigationBarColor;
+ private final int mWindowFlags;
+ private final int mWindowPrivateFlags;
+ private final float mScale;
+ private final @WindowInsets.Type.InsetsType int mRequestedVisibleTypes;
+ private final Rect mSystemBarInsets = new Rect();
+
+ public SystemBarBackgroundPainter(int windowFlags, int windowPrivateFlags, int appearance,
+ ActivityManager.TaskDescription taskDescription, float scale,
+ @WindowInsets.Type.InsetsType int requestedVisibleTypes) {
+ mWindowFlags = windowFlags;
+ mWindowPrivateFlags = windowPrivateFlags;
+ 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(), appearance,
+ APPEARANCE_LIGHT_STATUS_BARS,
+ taskDescription.getEnsureStatusBarContrastWhenTransparent());
+ mNavigationBarColor = DecorView.calculateBarColor(windowFlags,
+ FLAG_TRANSLUCENT_NAVIGATION, semiTransparent,
+ taskDescription.getNavigationBarColor(), appearance,
+ APPEARANCE_LIGHT_NAVIGATION_BARS,
+ taskDescription.getEnsureNavigationBarContrastWhenTransparent()
+ && context.getResources().getBoolean(
+ R.bool.config_navBarNeedsScrim));
+ mStatusBarPaint.setColor(mStatusBarColor);
+ mNavigationBarPaint.setColor(mNavigationBarColor);
+ mRequestedVisibleTypes = requestedVisibleTypes;
+ }
+
+ /**
+ * Set system bar insets.
+ */
+ public void setInsets(Rect systemBarInsets) {
+ mSystemBarInsets.set(systemBarInsets);
+ }
+
+ int getStatusBarColorViewHeight() {
+ final boolean forceBarBackground =
+ (mWindowPrivateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS) != 0;
+ if (STATUS_BAR_COLOR_VIEW_ATTRIBUTES.isVisible(
+ mRequestedVisibleTypes, mStatusBarColor, mWindowFlags,
+ forceBarBackground)) {
+ return (int) (mSystemBarInsets.top * mScale);
+ } else {
+ return 0;
+ }
+ }
+
+ private boolean isNavigationBarColorViewVisible() {
+ final boolean forceBarBackground =
+ (mWindowPrivateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS) != 0;
+ return NAVIGATION_BAR_COLOR_VIEW_ATTRIBUTES.isVisible(
+ mRequestedVisibleTypes, mNavigationBarColor, mWindowFlags,
+ forceBarBackground);
+ }
+
+ /**
+ * Draw bar colors to a canvas.
+ */
+ public void drawDecors(Canvas c, @Nullable Rect alreadyDrawnFrame) {
+ drawStatusBarBackground(c, alreadyDrawnFrame, getStatusBarColorViewHeight());
+ drawNavigationBarBackground(c);
+ }
+
+ void drawStatusBarBackground(Canvas c, @Nullable Rect alreadyDrawnFrame,
+ int statusBarHeight) {
+ if (statusBarHeight > 0 && Color.alpha(mStatusBarColor) != 0
+ && (alreadyDrawnFrame == null || c.getWidth() > alreadyDrawnFrame.right)) {
+ final int rightInset = (int) (mSystemBarInsets.right * mScale);
+ final int left = alreadyDrawnFrame != null ? alreadyDrawnFrame.right : 0;
+ c.drawRect(left, 0, c.getWidth() - rightInset, statusBarHeight,
+ mStatusBarPaint);
+ }
+ }
+
+ void drawNavigationBarBackground(Canvas c) {
+ final Rect navigationBarRect = new Rect();
+ getNavigationBarRect(c.getWidth(), c.getHeight(), mSystemBarInsets, navigationBarRect,
+ mScale);
+ final boolean visible = isNavigationBarColorViewVisible();
+ if (visible && Color.alpha(mNavigationBarColor) != 0
+ && !navigationBarRect.isEmpty()) {
+ c.drawRect(navigationBarRect, mNavigationBarPaint);
+ }
+ }
+ }
+}
diff --git a/core/java/android/window/StartingWindowRemovalInfo.java b/core/java/android/window/StartingWindowRemovalInfo.java
index 573db0d58625..384dacfe89ed 100644
--- a/core/java/android/window/StartingWindowRemovalInfo.java
+++ b/core/java/android/window/StartingWindowRemovalInfo.java
@@ -61,6 +61,12 @@ public final class StartingWindowRemovalInfo implements Parcelable {
*/
public boolean deferRemoveForIme;
+ /**
+ * The rounded corner radius
+ * @hide
+ */
+ public float roundedCornerRadius;
+
public StartingWindowRemovalInfo() {
}
@@ -80,6 +86,7 @@ public final class StartingWindowRemovalInfo implements Parcelable {
mainFrame = source.readTypedObject(Rect.CREATOR);
playRevealAnimation = source.readBoolean();
deferRemoveForIme = source.readBoolean();
+ roundedCornerRadius = source.readFloat();
}
@Override
@@ -89,6 +96,7 @@ public final class StartingWindowRemovalInfo implements Parcelable {
dest.writeTypedObject(mainFrame, flags);
dest.writeBoolean(playRevealAnimation);
dest.writeBoolean(deferRemoveForIme);
+ dest.writeFloat(roundedCornerRadius);
}
@Override
@@ -96,6 +104,7 @@ public final class StartingWindowRemovalInfo implements Parcelable {
return "StartingWindowRemovalInfo{taskId=" + taskId
+ " frame=" + mainFrame
+ " playRevealAnimation=" + playRevealAnimation
+ + " roundedCornerRadius=" + roundedCornerRadius
+ " deferRemoveForIme=" + deferRemoveForIme + "}";
}
diff --git a/core/java/android/window/TaskFragmentCreationParams.java b/core/java/android/window/TaskFragmentCreationParams.java
index 81ab7836435d..c9ddf92d3740 100644
--- a/core/java/android/window/TaskFragmentCreationParams.java
+++ b/core/java/android/window/TaskFragmentCreationParams.java
@@ -20,6 +20,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.app.WindowConfiguration.WindowingMode;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.TestApi;
import android.graphics.Rect;
import android.os.IBinder;
@@ -57,14 +58,33 @@ public final class TaskFragmentCreationParams implements Parcelable {
/** The initial windowing mode of the TaskFragment. Inherits from parent if not set. */
@WindowingMode
- private int mWindowingMode = WINDOWING_MODE_UNDEFINED;
+ private final int mWindowingMode;
+
+ /**
+ * The fragment token of the paired primary TaskFragment.
+ * When it is set, the new TaskFragment will be positioned right above the paired TaskFragment.
+ * Otherwise, the new TaskFragment will be positioned on the top of the Task by default.
+ *
+ * This is different from {@link WindowContainerTransaction#setAdjacentTaskFragments} as we may
+ * set this when the pair of TaskFragments are stacked, while adjacent is only set on the pair
+ * of TaskFragments that are in split.
+ *
+ * This is needed in case we need to launch a placeholder Activity to split below a transparent
+ * always-expand Activity.
+ */
+ @Nullable
+ private final IBinder mPairedPrimaryFragmentToken;
private TaskFragmentCreationParams(
- @NonNull TaskFragmentOrganizerToken organizer,
- @NonNull IBinder fragmentToken, @NonNull IBinder ownerToken) {
+ @NonNull TaskFragmentOrganizerToken organizer, @NonNull IBinder fragmentToken,
+ @NonNull IBinder ownerToken, @NonNull Rect initialBounds,
+ @WindowingMode int windowingMode, @Nullable IBinder pairedPrimaryFragmentToken) {
mOrganizer = organizer;
mFragmentToken = fragmentToken;
mOwnerToken = ownerToken;
+ mInitialBounds.set(initialBounds);
+ mWindowingMode = windowingMode;
+ mPairedPrimaryFragmentToken = pairedPrimaryFragmentToken;
}
@NonNull
@@ -92,12 +112,22 @@ public final class TaskFragmentCreationParams implements Parcelable {
return mWindowingMode;
}
+ /**
+ * TODO(b/232476698): remove the hide with adding CTS for this in next release.
+ * @hide
+ */
+ @Nullable
+ public IBinder getPairedPrimaryFragmentToken() {
+ return mPairedPrimaryFragmentToken;
+ }
+
private TaskFragmentCreationParams(Parcel in) {
mOrganizer = TaskFragmentOrganizerToken.CREATOR.createFromParcel(in);
mFragmentToken = in.readStrongBinder();
mOwnerToken = in.readStrongBinder();
mInitialBounds.readFromParcel(in);
mWindowingMode = in.readInt();
+ mPairedPrimaryFragmentToken = in.readStrongBinder();
}
/** @hide */
@@ -108,6 +138,7 @@ public final class TaskFragmentCreationParams implements Parcelable {
dest.writeStrongBinder(mOwnerToken);
mInitialBounds.writeToParcel(dest, flags);
dest.writeInt(mWindowingMode);
+ dest.writeStrongBinder(mPairedPrimaryFragmentToken);
}
@NonNull
@@ -132,6 +163,7 @@ public final class TaskFragmentCreationParams implements Parcelable {
+ " ownerToken=" + mOwnerToken
+ " initialBounds=" + mInitialBounds
+ " windowingMode=" + mWindowingMode
+ + " pairedFragmentToken=" + mPairedPrimaryFragmentToken
+ "}";
}
@@ -159,6 +191,9 @@ public final class TaskFragmentCreationParams implements Parcelable {
@WindowingMode
private int mWindowingMode = WINDOWING_MODE_UNDEFINED;
+ @Nullable
+ private IBinder mPairedPrimaryFragmentToken;
+
public Builder(@NonNull TaskFragmentOrganizerToken organizer,
@NonNull IBinder fragmentToken, @NonNull IBinder ownerToken) {
mOrganizer = organizer;
@@ -180,14 +215,29 @@ public final class TaskFragmentCreationParams implements Parcelable {
return this;
}
+ /**
+ * Sets the fragment token of the paired primary TaskFragment.
+ * When it is set, the new TaskFragment will be positioned right above the paired
+ * TaskFragment. Otherwise, the new TaskFragment will be positioned on the top of the Task
+ * by default.
+ *
+ * This is needed in case we need to launch a placeholder Activity to split below a
+ * transparent always-expand Activity.
+ *
+ * TODO(b/232476698): remove the hide with adding CTS for this in next release.
+ * @hide
+ */
+ @NonNull
+ public Builder setPairedPrimaryFragmentToken(@Nullable IBinder fragmentToken) {
+ mPairedPrimaryFragmentToken = fragmentToken;
+ return this;
+ }
+
/** Constructs the options to create TaskFragment with. */
@NonNull
public TaskFragmentCreationParams build() {
- final TaskFragmentCreationParams result = new TaskFragmentCreationParams(
- mOrganizer, mFragmentToken, mOwnerToken);
- result.mInitialBounds.set(mInitialBounds);
- result.mWindowingMode = mWindowingMode;
- return result;
+ return new TaskFragmentCreationParams(mOrganizer, mFragmentToken, mOwnerToken,
+ mInitialBounds, mWindowingMode, mPairedPrimaryFragmentToken);
}
}
}
diff --git a/core/java/android/window/WindowContainerToken.java b/core/java/android/window/WindowContainerToken.java
index 22b90b260f05..b914cb0dfd75 100644
--- a/core/java/android/window/WindowContainerToken.java
+++ b/core/java/android/window/WindowContainerToken.java
@@ -48,7 +48,6 @@ public final class WindowContainerToken implements Parcelable {
}
@Override
- /** @hide */
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeStrongBinder(mRealToken.asBinder());
}
@@ -68,7 +67,6 @@ public final class WindowContainerToken implements Parcelable {
};
@Override
- /** @hide */
public int describeContents() {
return 0;
}
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index 1063532af44f..5793674caaa6 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -827,6 +827,26 @@ public final class WindowContainerTransaction implements Parcelable {
}
/**
+ * Sets/removes the reparent leaf task flag for this {@code windowContainer}.
+ * When this is set, the server side will try to reparent the leaf task to task display area
+ * if there is an existing activity in history during the activity launch. This operation only
+ * support on the organized root task.
+ * @hide
+ */
+ @NonNull
+ public WindowContainerTransaction setReparentLeafTaskIfRelaunch(
+ @NonNull WindowContainerToken windowContainer, boolean reparentLeafTaskIfRelaunch) {
+ final HierarchyOp hierarchyOp =
+ new HierarchyOp.Builder(
+ HierarchyOp.HIERARCHY_OP_TYPE_SET_REPARENT_LEAF_TASK_IF_RELAUNCH)
+ .setContainer(windowContainer.asBinder())
+ .setReparentLeafTaskIfRelaunch(reparentLeafTaskIfRelaunch)
+ .build();
+ mHierarchyOps.add(hierarchyOp);
+ return this;
+ }
+
+ /**
* Merges another WCT into this one.
* @param transfer When true, this will transfer everything from other potentially leaving
* other in an unusable state. When false, other is left alone, but
@@ -905,7 +925,6 @@ public final class WindowContainerTransaction implements Parcelable {
}
@Override
- /** @hide */
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeMap(mChanges);
dest.writeTypedList(mHierarchyOps);
@@ -914,7 +933,6 @@ public final class WindowContainerTransaction implements Parcelable {
}
@Override
- /** @hide */
public int describeContents() {
return 0;
}
@@ -1242,6 +1260,7 @@ public final class WindowContainerTransaction implements Parcelable {
public static final int HIERARCHY_OP_TYPE_FINISH_ACTIVITY = 21;
public static final int HIERARCHY_OP_TYPE_SET_COMPANION_TASK_FRAGMENT = 22;
public static final int HIERARCHY_OP_TYPE_CLEAR_ADJACENT_ROOTS = 23;
+ public static final int HIERARCHY_OP_TYPE_SET_REPARENT_LEAF_TASK_IF_RELAUNCH = 24;
// The following key(s) are for use with mLaunchOptions:
// When launching a task (eg. from recents), this is the taskId to be launched.
@@ -1294,6 +1313,8 @@ public final class WindowContainerTransaction implements Parcelable {
private boolean mAlwaysOnTop;
+ private boolean mReparentLeafTaskIfRelaunch;
+
public static HierarchyOp createForReparent(
@NonNull IBinder container, @Nullable IBinder reparent, boolean toTop) {
return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_REPARENT)
@@ -1406,6 +1427,7 @@ public final class WindowContainerTransaction implements Parcelable {
mPendingIntent = copy.mPendingIntent;
mShortcutInfo = copy.mShortcutInfo;
mAlwaysOnTop = copy.mAlwaysOnTop;
+ mReparentLeafTaskIfRelaunch = copy.mReparentLeafTaskIfRelaunch;
}
protected HierarchyOp(Parcel in) {
@@ -1428,6 +1450,7 @@ public final class WindowContainerTransaction implements Parcelable {
mPendingIntent = in.readTypedObject(PendingIntent.CREATOR);
mShortcutInfo = in.readTypedObject(ShortcutInfo.CREATOR);
mAlwaysOnTop = in.readBoolean();
+ mReparentLeafTaskIfRelaunch = in.readBoolean();
}
public int getType() {
@@ -1502,6 +1525,10 @@ public final class WindowContainerTransaction implements Parcelable {
return mAlwaysOnTop;
}
+ public boolean isReparentLeafTaskIfRelaunch() {
+ return mReparentLeafTaskIfRelaunch;
+ }
+
@Nullable
public TaskFragmentCreationParams getTaskFragmentCreationOptions() {
return mTaskFragmentCreationOptions;
@@ -1582,6 +1609,9 @@ public final class WindowContainerTransaction implements Parcelable {
+ mReparent + "}";
case HIERARCHY_OP_TYPE_CLEAR_ADJACENT_ROOTS:
return "{ClearAdjacentRoot: container=" + mContainer + "}";
+ case HIERARCHY_OP_TYPE_SET_REPARENT_LEAF_TASK_IF_RELAUNCH:
+ return "{setReparentLeafTaskIfRelaunch: container= " + mContainer
+ + " reparentLeafTaskIfRelaunch= " + mReparentLeafTaskIfRelaunch + "}";
default:
return "{mType=" + mType + " container=" + mContainer + " reparent=" + mReparent
+ " mToTop=" + mToTop
@@ -1612,6 +1642,7 @@ public final class WindowContainerTransaction implements Parcelable {
dest.writeTypedObject(mPendingIntent, flags);
dest.writeTypedObject(mShortcutInfo, flags);
dest.writeBoolean(mAlwaysOnTop);
+ dest.writeBoolean(mReparentLeafTaskIfRelaunch);
}
@Override
@@ -1672,6 +1703,8 @@ public final class WindowContainerTransaction implements Parcelable {
private boolean mAlwaysOnTop;
+ private boolean mReparentLeafTaskIfRelaunch;
+
Builder(int type) {
mType = type;
}
@@ -1742,6 +1775,11 @@ public final class WindowContainerTransaction implements Parcelable {
return this;
}
+ Builder setReparentLeafTaskIfRelaunch(boolean reparentLeafTaskIfRelaunch) {
+ mReparentLeafTaskIfRelaunch = reparentLeafTaskIfRelaunch;
+ return this;
+ }
+
Builder setShortcutInfo(@Nullable ShortcutInfo shortcutInfo) {
mShortcutInfo = shortcutInfo;
return this;
@@ -1767,6 +1805,7 @@ public final class WindowContainerTransaction implements Parcelable {
hierarchyOp.mAlwaysOnTop = mAlwaysOnTop;
hierarchyOp.mTaskFragmentCreationOptions = mTaskFragmentCreationOptions;
hierarchyOp.mShortcutInfo = mShortcutInfo;
+ hierarchyOp.mReparentLeafTaskIfRelaunch = mReparentLeafTaskIfRelaunch;
return hierarchyOp;
}
diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index fda39c14dac7..dd9483a9c759 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -229,19 +229,21 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
}
@Override
- public void onBackStarted(BackEvent backEvent) {
+ public void onBackStarted(BackMotionEvent backEvent) {
Handler.getMain().post(() -> {
final OnBackAnimationCallback callback = getBackAnimationCallback();
if (callback != null) {
mProgressAnimator.onBackStarted(backEvent, event ->
callback.onBackProgressed(event));
- callback.onBackStarted(backEvent);
+ callback.onBackStarted(new BackEvent(
+ backEvent.getTouchX(), backEvent.getTouchY(),
+ backEvent.getProgress(), backEvent.getSwipeEdge()));
}
});
}
@Override
- public void onBackProgressed(BackEvent backEvent) {
+ public void onBackProgressed(BackMotionEvent backEvent) {
Handler.getMain().post(() -> {
final OnBackAnimationCallback callback = getBackAnimationCallback();
if (callback != null) {
diff --git a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
index 43be0312245e..e0b01103ae73 100644
--- a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
+++ b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
@@ -226,9 +226,7 @@ public class AccessibilityShortcutController {
Slog.d(TAG, "Accessibility shortcut activated");
final ContentResolver cr = mContext.getContentResolver();
final int userId = ActivityManager.getCurrentUser();
- final int dialogAlreadyShown = Settings.Secure.getIntForUser(
- cr, Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, DialogStatus.NOT_SHOWN,
- userId);
+
// Play a notification vibration
Vibrator vibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE);
if ((vibrator != null) && vibrator.hasVibrator()) {
@@ -239,7 +237,7 @@ public class AccessibilityShortcutController {
vibrator.vibrate(vibePattern, -1, VIBRATION_ATTRIBUTES);
}
- if (dialogAlreadyShown == DialogStatus.NOT_SHOWN) {
+ if (shouldShowDialog()) {
// The first time, we show a warning rather than toggle the service to give the user a
// chance to turn off this feature before stuff gets enabled.
mAlertDialog = createShortcutWarningDialog(userId);
@@ -269,6 +267,20 @@ public class AccessibilityShortcutController {
}
}
+ /** Whether the warning dialog should be shown instead of performing the shortcut. */
+ private boolean shouldShowDialog() {
+ if (hasFeatureLeanback()) {
+ // Never show the dialog on TV, instead always perform the shortcut directly.
+ return false;
+ }
+ final ContentResolver cr = mContext.getContentResolver();
+ final int userId = ActivityManager.getCurrentUser();
+ final int dialogAlreadyShown = Settings.Secure.getIntForUser(cr,
+ Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, DialogStatus.NOT_SHOWN,
+ userId);
+ return dialogAlreadyShown == DialogStatus.NOT_SHOWN;
+ }
+
/**
* Show toast to alert the user that the accessibility shortcut turned on or off an
* accessibility service.
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index 292a50b77ecc..4f7f8ba2b45c 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -556,6 +556,12 @@ public final class SystemUiDeviceConfigFlags {
"show_stop_button_for_user_allowlisted_apps";
/**
+ * (boolean) Whether the task manager should show apps running user-visible jobs.
+ */
+ public static final String TASK_MANAGER_SHOW_USER_VISIBLE_JOBS =
+ "task_manager_show_user_visible_jobs";
+
+ /**
* (boolean) Whether to show notification volume control slider separate from ring.
*/
public static final String VOLUME_SEPARATE_NOTIFICATION = "volume_separate_notification";
diff --git a/core/java/com/android/internal/content/om/TEST_MAPPING b/core/java/com/android/internal/content/om/TEST_MAPPING
index 4cb595b01681..98dadce7359a 100644
--- a/core/java/com/android/internal/content/om/TEST_MAPPING
+++ b/core/java/com/android/internal/content/om/TEST_MAPPING
@@ -7,6 +7,28 @@
"include-filter": "com.android.internal.content."
}
]
+ },
+ {
+ "name": "SelfTargetingOverlayDeviceTests"
+ }
+ ],
+ "presubmit-large": [
+ {
+ "name": "CtsContentTestCases",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ },
+ {
+ "include-filter": "android.content.om.cts"
+ },
+ {
+ "include-filter": "android.content.res.loader.cts"
+ }
+ ]
}
]
-} \ No newline at end of file
+}
diff --git a/core/java/com/android/internal/inputmethod/EditableInputConnection.java b/core/java/com/android/internal/inputmethod/EditableInputConnection.java
index 330ff8e61609..52e747150789 100644
--- a/core/java/com/android/internal/inputmethod/EditableInputConnection.java
+++ b/core/java/com/android/internal/inputmethod/EditableInputConnection.java
@@ -27,6 +27,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.graphics.RectF;
import android.os.Bundle;
+import android.os.CancellationSignal;
import android.text.Editable;
import android.text.Selection;
import android.text.method.KeyListener;
@@ -44,6 +45,7 @@ import android.view.inputmethod.HandwritingGesture;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InsertGesture;
import android.view.inputmethod.JoinOrSplitGesture;
+import android.view.inputmethod.PreviewableHandwritingGesture;
import android.view.inputmethod.RemoveSpaceGesture;
import android.view.inputmethod.SelectGesture;
import android.view.inputmethod.SelectRangeGesture;
@@ -321,6 +323,13 @@ public final class EditableInputConnection extends BaseInputConnection
}
@Override
+ public boolean previewHandwritingGesture(
+ @NonNull PreviewableHandwritingGesture gesture,
+ @Nullable CancellationSignal cancellationSignal) {
+ return mTextView.previewHandwritingGesture(gesture, cancellationSignal);
+ }
+
+ @Override
public void dumpDebug(ProtoOutputStream proto, long fieldId) {
final long token = proto.start(fieldId);
CharSequence editableText = mTextView.getText();
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index 614f96255acb..75f0bf574947 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -97,6 +97,7 @@ import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.HandlerThread;
import android.provider.DeviceConfig;
+import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
import android.util.SparseArray;
@@ -415,7 +416,7 @@ public class InteractionJankMonitor {
@VisibleForTesting
public InteractionJankMonitor(@NonNull HandlerThread worker) {
// Check permission early.
- DeviceConfig.enforceReadPermission(
+ Settings.Config.enforceReadPermission(
DeviceConfig.NAMESPACE_INTERACTION_JANK_MONITOR);
mRunningTrackers = new SparseArray<>();
diff --git a/core/java/com/android/internal/os/anr/AnrLatencyTracker.java b/core/java/com/android/internal/os/anr/AnrLatencyTracker.java
index a182920cbf7a..2748dedf356e 100644
--- a/core/java/com/android/internal/os/anr/AnrLatencyTracker.java
+++ b/core/java/com/android/internal/os/anr/AnrLatencyTracker.java
@@ -24,6 +24,7 @@ import static com.android.internal.util.FrameworkStatsLog.ANRLATENCY_REPORTED__A
import static com.android.internal.util.FrameworkStatsLog.ANRLATENCY_REPORTED__ANR_TYPE__EXECUTING_SERVICE;
import static com.android.internal.util.FrameworkStatsLog.ANRLATENCY_REPORTED__ANR_TYPE__INPUT_DISPATCHING_TIMEOUT;
import static com.android.internal.util.FrameworkStatsLog.ANRLATENCY_REPORTED__ANR_TYPE__INPUT_DISPATCHING_TIMEOUT_NO_FOCUSED_WINDOW;
+import static com.android.internal.util.FrameworkStatsLog.ANRLATENCY_REPORTED__ANR_TYPE__SHORT_FGS_TIMEOUT;
import static com.android.internal.util.FrameworkStatsLog.ANRLATENCY_REPORTED__ANR_TYPE__START_FOREGROUND_SERVICE;
import static com.android.internal.util.FrameworkStatsLog.ANRLATENCY_REPORTED__ANR_TYPE__UNKNOWN_ANR_TYPE;
@@ -400,6 +401,8 @@ public class AnrLatencyTracker implements AutoCloseable {
return ANRLATENCY_REPORTED__ANR_TYPE__EXECUTING_SERVICE;
case TimeoutKind.CONTENT_PROVIDER:
return ANRLATENCY_REPORTED__ANR_TYPE__CONTENT_PROVIDER_NOT_RESPONDING;
+ case TimeoutKind.SHORT_FGS_TIMEOUT:
+ return ANRLATENCY_REPORTED__ANR_TYPE__SHORT_FGS_TIMEOUT;
default:
return ANRLATENCY_REPORTED__ANR_TYPE__UNKNOWN_ANR_TYPE;
}
diff --git a/core/java/com/android/internal/power/ModemPowerProfile.java b/core/java/com/android/internal/power/ModemPowerProfile.java
index afea69a9c3f2..a555ae38736e 100644
--- a/core/java/com/android/internal/power/ModemPowerProfile.java
+++ b/core/java/com/android/internal/power/ModemPowerProfile.java
@@ -158,11 +158,11 @@ public class ModemPowerProfile {
private static final SparseArray<String> MODEM_TX_LEVEL_NAMES = new SparseArray<>(5);
static {
- MODEM_DRAIN_TYPE_NAMES.put(MODEM_TX_LEVEL_0, "0");
- MODEM_DRAIN_TYPE_NAMES.put(MODEM_TX_LEVEL_1, "1");
- MODEM_DRAIN_TYPE_NAMES.put(MODEM_TX_LEVEL_2, "2");
- MODEM_DRAIN_TYPE_NAMES.put(MODEM_TX_LEVEL_3, "3");
- MODEM_DRAIN_TYPE_NAMES.put(MODEM_TX_LEVEL_4, "4");
+ MODEM_TX_LEVEL_NAMES.put(MODEM_TX_LEVEL_0, "0");
+ MODEM_TX_LEVEL_NAMES.put(MODEM_TX_LEVEL_1, "1");
+ MODEM_TX_LEVEL_NAMES.put(MODEM_TX_LEVEL_2, "2");
+ MODEM_TX_LEVEL_NAMES.put(MODEM_TX_LEVEL_3, "3");
+ MODEM_TX_LEVEL_NAMES.put(MODEM_TX_LEVEL_4, "4");
}
private static final int[] MODEM_TX_LEVEL_MAP = new int[]{
@@ -418,7 +418,10 @@ public class ModemPowerProfile {
return Double.NaN;
}
- private static String keyToString(int key) {
+ /**
+ * Returns a human readable version of a key.
+ */
+ public static String keyToString(int key) {
StringBuilder sb = new StringBuilder();
final int drainType = key & MODEM_DRAIN_TYPE_MASK;
appendFieldToString(sb, "drain", MODEM_DRAIN_TYPE_NAMES, drainType);
@@ -427,6 +430,7 @@ public class ModemPowerProfile {
if (drainType == MODEM_DRAIN_TYPE_TX) {
final int txLevel = key & MODEM_TX_LEVEL_MASK;
appendFieldToString(sb, "level", MODEM_TX_LEVEL_NAMES, txLevel);
+ sb.append(",");
}
final int ratType = key & MODEM_RAT_TYPE_MASK;
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index ebfc4f75fe2a..db288c0dca90 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -327,4 +327,14 @@ oneway interface IStatusBar
/** Shows rear display educational dialog */
void showRearDisplayDialog(int currentBaseState);
+
+ /** Called when requested to go to fullscreen from the active split app. */
+ void goToFullscreenFromSplit();
+
+ /**
+ * Enters stage split from a current running app.
+ *
+ * @param leftOrTop indicates where the stage split is.
+ */
+ void enterStageSplitFromRunningApp(boolean leftOrTop);
}
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 1bc903a191ad..a43f0b38ddce 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -339,6 +339,9 @@ cc_library_shared {
"dnsproxyd_protocol_headers",
"libtextclassifier_hash_headers",
],
+ runtime_libs: [
+ "libidmap2",
+ ],
},
host: {
cflags: [
diff --git a/core/jni/TEST_MAPPING b/core/jni/TEST_MAPPING
index 004c30ee5113..28448561e135 100644
--- a/core/jni/TEST_MAPPING
+++ b/core/jni/TEST_MAPPING
@@ -11,6 +11,29 @@
}
],
"file_patterns": ["CharsetUtils|FastData"]
+ },
+ {
+ "name": "SelfTargetingOverlayDeviceTests",
+ "file_patterns": ["Overlay"]
+ }
+ ],
+ "presubmit-large": [
+ {
+ "name": "CtsContentTestCases",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ },
+ {
+ "include-filter": "android.content.om.cts"
+ },
+ {
+ "include-filter": "android.content.res.loader.cts"
+ }
+ ]
}
]
}
diff --git a/core/jni/android_view_DisplayEventReceiver.cpp b/core/jni/android_view_DisplayEventReceiver.cpp
index 019e3bd32be9..a8d8a431666a 100644
--- a/core/jni/android_view_DisplayEventReceiver.cpp
+++ b/core/jni/android_view_DisplayEventReceiver.cpp
@@ -80,7 +80,7 @@ private:
VsyncEventData vsyncEventData) override;
void dispatchHotplug(nsecs_t timestamp, PhysicalDisplayId displayId, bool connected) override;
void dispatchModeChanged(nsecs_t timestamp, PhysicalDisplayId displayId, int32_t modeId,
- nsecs_t vsyncPeriod) override;
+ nsecs_t renderPeriod) override;
void dispatchFrameRateOverrides(nsecs_t timestamp, PhysicalDisplayId displayId,
std::vector<FrameRateOverride> overrides) override;
void dispatchNullEvent(nsecs_t timestamp, PhysicalDisplayId displayId) override {}
@@ -168,14 +168,14 @@ void NativeDisplayEventReceiver::dispatchHotplug(nsecs_t timestamp, PhysicalDisp
}
void NativeDisplayEventReceiver::dispatchModeChanged(nsecs_t timestamp, PhysicalDisplayId displayId,
- int32_t modeId, nsecs_t) {
+ int32_t modeId, nsecs_t renderPeriod) {
JNIEnv* env = AndroidRuntime::getJNIEnv();
ScopedLocalRef<jobject> receiverObj(env, GetReferent(env, mReceiverWeakGlobal));
if (receiverObj.get()) {
ALOGV("receiver %p ~ Invoking mode changed handler.", this);
env->CallVoidMethod(receiverObj.get(), gDisplayEventReceiverClassInfo.dispatchModeChanged,
- timestamp, displayId.value, modeId);
+ timestamp, displayId.value, modeId, renderPeriod);
ALOGV("receiver %p ~ Returned from mode changed handler.", this);
}
@@ -290,7 +290,7 @@ int register_android_view_DisplayEventReceiver(JNIEnv* env) {
gDisplayEventReceiverClassInfo.clazz, "dispatchHotplug", "(JJZ)V");
gDisplayEventReceiverClassInfo.dispatchModeChanged =
GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.clazz, "dispatchModeChanged",
- "(JJI)V");
+ "(JJIJ)V");
gDisplayEventReceiverClassInfo.dispatchFrameRateOverrides =
GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.clazz,
"dispatchFrameRateOverrides",
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 5a0a84b9a4ba..593482c9deac 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -109,6 +109,7 @@ static struct {
jmethodID ctor;
jfieldID supportedDisplayModes;
jfieldID activeDisplayModeId;
+ jfieldID renderFrameRate;
jfieldID supportedColorModes;
jfieldID activeColorMode;
jfieldID hdrCapabilities;
@@ -129,6 +130,7 @@ static struct {
jfieldID appVsyncOffsetNanos;
jfieldID presentationDeadlineNanos;
jfieldID group;
+ jfieldID supportedHdrTypes;
} gDisplayModeClassInfo;
// Implements SkMallocPixelRef::ReleaseProc, to delete the screenshot on unref.
@@ -1097,10 +1099,9 @@ static jobject convertDeviceProductInfoToJavaObject(
connectionToSinkType);
}
-static jobject nativeGetStaticDisplayInfo(JNIEnv* env, jclass clazz, jobject tokenObj) {
+static jobject nativeGetStaticDisplayInfo(JNIEnv* env, jclass clazz, jlong id) {
ui::StaticDisplayInfo info;
- if (const auto token = ibinderForJavaObject(env, tokenObj);
- !token || SurfaceComposerClient::getStaticDisplayInfo(token, &info) != NO_ERROR) {
+ if (SurfaceComposerClient::getStaticDisplayInfo(id, &info) != NO_ERROR) {
return nullptr;
}
@@ -1130,6 +1131,16 @@ static jobject convertDisplayModeToJavaObject(JNIEnv* env, const ui::DisplayMode
env->SetLongField(object, gDisplayModeClassInfo.presentationDeadlineNanos,
config.presentationDeadline);
env->SetIntField(object, gDisplayModeClassInfo.group, config.group);
+
+ const auto& types = config.supportedHdrTypes;
+ std::vector<jint> intTypes;
+ for (auto type : types) {
+ intTypes.push_back(static_cast<jint>(type));
+ }
+ auto typesArray = env->NewIntArray(types.size());
+ env->SetIntArrayRegion(typesArray, 0, intTypes.size(), intTypes.data());
+ env->SetObjectField(object, gDisplayModeClassInfo.supportedHdrTypes, typesArray);
+
return object;
}
@@ -1148,10 +1159,9 @@ jobject convertHdrCapabilitiesToJavaObject(JNIEnv* env, const HdrCapabilities& c
capabilities.getDesiredMinLuminance());
}
-static jobject nativeGetDynamicDisplayInfo(JNIEnv* env, jclass clazz, jobject tokenObj) {
+static jobject nativeGetDynamicDisplayInfo(JNIEnv* env, jclass clazz, jlong displayId) {
ui::DynamicDisplayInfo info;
- if (const auto token = ibinderForJavaObject(env, tokenObj);
- !token || SurfaceComposerClient::getDynamicDisplayInfo(token, &info) != NO_ERROR) {
+ if (SurfaceComposerClient::getDynamicDisplayInfoFromId(displayId, &info) != NO_ERROR) {
return nullptr;
}
@@ -1173,6 +1183,7 @@ static jobject nativeGetDynamicDisplayInfo(JNIEnv* env, jclass clazz, jobject to
env->SetObjectField(object, gDynamicDisplayInfoClassInfo.supportedDisplayModes, modesArray);
env->SetIntField(object, gDynamicDisplayInfoClassInfo.activeDisplayModeId,
info.activeDisplayModeId);
+ env->SetFloatField(object, gDynamicDisplayInfoClassInfo.renderFrameRate, info.renderFrameRate);
jintArray colorModesArray = env->NewIntArray(info.supportedColorModes.size());
if (colorModesArray == NULL) {
@@ -2007,10 +2018,10 @@ static const JNINativeMethod sSurfaceControlMethods[] = {
{"nativeSetDisplaySize", "(JLandroid/os/IBinder;II)V",
(void*)nativeSetDisplaySize },
{"nativeGetStaticDisplayInfo",
- "(Landroid/os/IBinder;)Landroid/view/SurfaceControl$StaticDisplayInfo;",
+ "(J)Landroid/view/SurfaceControl$StaticDisplayInfo;",
(void*)nativeGetStaticDisplayInfo },
{"nativeGetDynamicDisplayInfo",
- "(Landroid/os/IBinder;)Landroid/view/SurfaceControl$DynamicDisplayInfo;",
+ "(J)Landroid/view/SurfaceControl$DynamicDisplayInfo;",
(void*)nativeGetDynamicDisplayInfo },
{"nativeSetDesiredDisplayModeSpecs",
"(Landroid/os/IBinder;Landroid/view/SurfaceControl$DesiredDisplayModeSpecs;)Z",
@@ -2163,6 +2174,8 @@ int register_android_view_SurfaceControl(JNIEnv* env)
"[Landroid/view/SurfaceControl$DisplayMode;");
gDynamicDisplayInfoClassInfo.activeDisplayModeId =
GetFieldIDOrDie(env, dynamicInfoClazz, "activeDisplayModeId", "I");
+ gDynamicDisplayInfoClassInfo.renderFrameRate =
+ GetFieldIDOrDie(env, dynamicInfoClazz, "renderFrameRate", "F");
gDynamicDisplayInfoClassInfo.supportedColorModes =
GetFieldIDOrDie(env, dynamicInfoClazz, "supportedColorModes", "[I");
gDynamicDisplayInfoClassInfo.activeColorMode =
@@ -2191,6 +2204,8 @@ int register_android_view_SurfaceControl(JNIEnv* env)
gDisplayModeClassInfo.presentationDeadlineNanos =
GetFieldIDOrDie(env, modeClazz, "presentationDeadlineNanos", "J");
gDisplayModeClassInfo.group = GetFieldIDOrDie(env, modeClazz, "group", "I");
+ gDisplayModeClassInfo.supportedHdrTypes =
+ GetFieldIDOrDie(env, modeClazz, "supportedHdrTypes", "[I");
jclass frameStatsClazz = FindClassOrDie(env, "android/view/FrameStats");
jfieldID undefined_time_nano_field = GetStaticFieldIDOrDie(env,
diff --git a/core/proto/android/server/activitymanagerservice.proto b/core/proto/android/server/activitymanagerservice.proto
index 7393c6f9324a..18d84d50aa7d 100644
--- a/core/proto/android/server/activitymanagerservice.proto
+++ b/core/proto/android/server/activitymanagerservice.proto
@@ -457,6 +457,7 @@ message ServiceRecordProto {
optional bool stop_if_killed = 3;
optional bool call_start = 4;
optional int32 last_start_id = 5;
+ optional int32 start_command_result = 6;
}
optional Start start = 19;
@@ -499,7 +500,21 @@ message ServiceRecordProto {
repeated ConnectionRecordProto connections = 26;
optional bool allow_while_in_use_permission_in_fgs = 27;
- // Next Tag: 28
+
+ message ShortFgsInfo {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ optional int64 start_time = 1;
+ optional int32 start_foreground_count = 2;
+ optional int32 start_id = 3;
+ optional int64 timeout_time = 4;
+ optional int64 proc_state_demote_time = 5;
+ optional int64 anr_time = 6;
+ }
+
+ optional ShortFgsInfo short_fgs_info = 28;
+
+ // Next Tag: 29
}
message ConnectionRecordProto {
diff --git a/core/res/Android.bp b/core/res/Android.bp
index 7e17840445ab..3c4bac8156d2 100644
--- a/core/res/Android.bp
+++ b/core/res/Android.bp
@@ -107,7 +107,9 @@ android_app {
sdk_version: "core_platform",
certificate: "platform",
- srcs: [":remote-color-resources-arsc"],
+ srcs: [
+ ":remote-color-resources-arsc",
+ ],
// Disable dexpreopt and verify_uses_libraries check as the app
// contains no Java code to be dexpreopted.
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index ad8f7fb2d425..98b69ed2900a 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3327,7 +3327,9 @@
<p>Protection level: normal
-->
<permission android:name="android.permission.REQUEST_COMPANION_START_FOREGROUND_SERVICES_FROM_BACKGROUND"
- android:protectionLevel="normal"/>
+ android:label="@string/permlab_startForegroundServicesFromBackground"
+ android:description="@string/permdesc_startForegroundServicesFromBackground"
+ android:protectionLevel="normal"/>
<!-- Allows a companion app to use data in the background.
<p>Protection level: normal
@@ -3343,6 +3345,8 @@
<p>Protection level: normal
-->
<permission android:name="android.permission.REQUEST_COMPANION_PROFILE_WATCH"
+ android:label="@string/permlab_companionProfileWatch"
+ android:description="@string/permdesc_companionProfileWatch"
android:protectionLevel="normal" />
<!-- Allows application to request to be associated with a virtual display capable of streaming
@@ -4625,6 +4629,8 @@
<p>Protection level: appop
-->
<permission android:name="android.permission.SCHEDULE_EXACT_ALARM"
+ android:label="@string/permlab_schedule_exact_alarm"
+ android:description="@string/permdesc_schedule_exact_alarm"
android:protectionLevel="normal|appop"/>
<!-- Allows apps to use exact alarms just like with {@link
@@ -4650,6 +4656,8 @@
lower standby bucket.
-->
<permission android:name="android.permission.USE_EXACT_ALARM"
+ android:label="@string/permlab_use_exact_alarm"
+ android:description="@string/permdesc_use_exact_alarm"
android:protectionLevel="normal"/>
<!-- Allows an application to query tablet mode state and monitor changes
@@ -4914,11 +4922,15 @@
of their associated companion device
-->
<permission android:name="android.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE"
+ android:label="@string/permlab_observeCompanionDevicePresence"
+ android:description="@string/permdesc_observeCompanionDevicePresence"
android:protectionLevel="normal" />
<!-- Allows an application to deliver companion messages to system
-->
<permission android:name="android.permission.DELIVER_COMPANION_MESSAGES"
+ android:label="@string/permlab_deliverCompanionMessages"
+ android:description="@string/permdesc_deliverCompanionMessages"
android:protectionLevel="normal" />
<!-- Allows an application to create new companion device associations.
@@ -6286,6 +6298,15 @@
android:protectionLevel="normal|instant" />
<!-- Allows a regular application to use {@link android.app.Service#startForeground
+ Service.startForeground} with the type "fileManagement".
+ <p>Protection level: normal|instant
+ -->
+ <permission android:name="android.permission.FOREGROUND_SERVICE_FILE_MANAGEMENT"
+ android:description="@string/permdesc_foregroundServiceFileManagement"
+ android:label="@string/permlab_foregroundServiceFileManagement"
+ android:protectionLevel="normal|instant" />
+
+ <!-- Allows a regular application to use {@link android.app.Service#startForeground
Service.startForeground} with the type "specialUse".
<p>Protection level: normal|appop|instant
-->
@@ -6755,6 +6776,14 @@
@hide -->
<permission android:name="android.permission.HANDLE_QUERY_PACKAGE_RESTART"
android:protectionLevel="signature" />
+
+ <!-- Allows low-level access to re-mapping modifier keys.
+ <p>Not for use by third-party applications.
+ @hide
+ @TestApi -->
+ <permission android:name="android.permission.REMAP_MODIFIER_KEYS"
+ android:protectionLevel="signature" />
+
<uses-permission android:name="android.permission.HANDLE_QUERY_PACKAGE_RESTART" />
<!-- Allows financed device kiosk apps to perform actions on the Device Lock service
@@ -7329,6 +7358,10 @@
android:permission="android.permission.BIND_JOB_SERVICE" >
</service>
+ <service android:name="com.android.server.healthconnect.storage.AutoDeleteService"
+ android:permission="android.permission.BIND_JOB_SERVICE" >
+ </service>
+
<service android:name="com.android.server.pm.PackageManagerShellCommandDataLoader"
android:exported="false">
<intent-filter>
diff --git a/core/res/assets/geoid_height_map/README.md b/core/res/assets/geoid_height_map/README.md
new file mode 100644
index 000000000000..849d32ec34aa
--- /dev/null
+++ b/core/res/assets/geoid_height_map/README.md
@@ -0,0 +1,2 @@
+These binary protos are generated at runtime from the text protos in ../../geoid_height_map_assets
+and using aprotoc. \ No newline at end of file
diff --git a/core/res/assets/geoid_height_map/map-params.pb b/core/res/assets/geoid_height_map/map-params.pb
new file mode 100644
index 000000000000..6fd4022fa326
--- /dev/null
+++ b/core/res/assets/geoid_height_map/map-params.pb
Binary files differ
diff --git a/core/res/assets/geoid_height_map/tile-1.pb b/core/res/assets/geoid_height_map/tile-1.pb
new file mode 100644
index 000000000000..546dd0d46616
--- /dev/null
+++ b/core/res/assets/geoid_height_map/tile-1.pb
Binary files differ
diff --git a/core/res/assets/geoid_height_map/tile-3.pb b/core/res/assets/geoid_height_map/tile-3.pb
new file mode 100644
index 000000000000..eb3fe465e274
--- /dev/null
+++ b/core/res/assets/geoid_height_map/tile-3.pb
Binary files differ
diff --git a/core/res/assets/geoid_height_map/tile-5.pb b/core/res/assets/geoid_height_map/tile-5.pb
new file mode 100644
index 000000000000..0243d6d082aa
--- /dev/null
+++ b/core/res/assets/geoid_height_map/tile-5.pb
Binary files differ
diff --git a/core/res/assets/geoid_height_map/tile-7.pb b/core/res/assets/geoid_height_map/tile-7.pb
new file mode 100644
index 000000000000..3c2f777ad77e
--- /dev/null
+++ b/core/res/assets/geoid_height_map/tile-7.pb
Binary files differ
diff --git a/core/res/assets/geoid_height_map/tile-9.pb b/core/res/assets/geoid_height_map/tile-9.pb
new file mode 100644
index 000000000000..5e9a4808eae7
--- /dev/null
+++ b/core/res/assets/geoid_height_map/tile-9.pb
Binary files differ
diff --git a/core/res/assets/geoid_height_map/tile-b.pb b/core/res/assets/geoid_height_map/tile-b.pb
new file mode 100644
index 000000000000..c57e8732b61d
--- /dev/null
+++ b/core/res/assets/geoid_height_map/tile-b.pb
Binary files differ
diff --git a/core/res/geoid_height_map_assets/README.md b/core/res/geoid_height_map_assets/README.md
new file mode 100644
index 000000000000..800b3e50795a
--- /dev/null
+++ b/core/res/geoid_height_map_assets/README.md
@@ -0,0 +1,8 @@
+These text protos contain composite JPEG/PNG images representing the EGM2008 Earth Gravitational
+Model[^1] published by the National Geospatial-Intelligence Agency.[^2]
+
+[^1]: Pavlis, Nikolaos K., et al. "The development and evaluation of the Earth Gravitational Model
+2008 (EGM2008)." Journal of geophysical research: solid earth 117.B4 (2012).
+
+[^2]: National Geospatial-Intelligence Agency. “Office of Geomatics.” 2022.
+URL: https://earth-info.nga.mil. \ No newline at end of file
diff --git a/core/res/geoid_height_map_assets/map-params.textpb b/core/res/geoid_height_map_assets/map-params.textpb
new file mode 100644
index 000000000000..3f504d4f89f3
--- /dev/null
+++ b/core/res/geoid_height_map_assets/map-params.textpb
@@ -0,0 +1,6 @@
+map_s2_level: 9
+cache_tile_s2_level: 5
+disk_tile_s2_level: 0
+model_a_meters: 255.0
+model_b_meters: -128.0
+model_rmse_meters: 0.36
diff --git a/core/res/geoid_height_map_assets/tile-1.textpb b/core/res/geoid_height_map_assets/tile-1.textpb
new file mode 100644
index 000000000000..7fac2347daa3
--- /dev/null
+++ b/core/res/geoid_height_map_assets/tile-1.textpb
@@ -0,0 +1,3 @@
+tile_key: "1"
+byte_jpeg: "\377\330\377\340\000\020JFIF\000\001\002\000\000\001\000\001\000\000\377\333\000C\000\004\003\003\004\003\003\004\004\003\004\005\004\004\005\006\n\007\006\006\006\006\r\t\n\010\n\017\r\020\020\017\r\017\016\021\023\030\024\021\022\027\022\016\017\025\034\025\027\031\031\033\033\033\020\024\035\037\035\032\037\030\032\033\032\377\300\000\013\010\002\000\002\000\001\001\021\000\377\304\000\037\000\000\001\005\001\001\001\001\001\001\000\000\000\000\000\000\000\000\001\002\003\004\005\006\007\010\t\n\013\377\304\000\265\020\000\002\001\003\003\002\004\003\005\005\004\004\000\000\001}\001\002\003\000\004\021\005\022!1A\006\023Qa\007\"q\0242\201\221\241\010#B\261\301\025R\321\360$3br\202\t\n\026\027\030\031\032%&\'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz\203\204\205\206\207\210\211\212\222\223\224\225\226\227\230\231\232\242\243\244\245\246\247\250\251\252\262\263\264\265\266\267\270\271\272\302\303\304\305\306\307\310\311\312\322\323\324\325\326\327\330\331\332\341\342\343\344\345\346\347\350\351\352\361\362\363\364\365\366\367\370\371\372\377\332\000\010\001\001\000\000?\000\244i(\242\224R\216\264\361OZ\221je\251V\246Z\225jE\251V\245Z\225jE\247\216\324\361N\317\024f\214\322\023I\2323I\232L\322f\220\232LdqM\316:\321E\024\354\344sJ)\364\340i\340S\200\315(\030\243\255:\224\032p4\264\340i\300\324\213\315:\232EFz\320\0174\244\323I\246\223M-F\3527R\026\244\rR+S\353\314\r%\024QN\247\212z\324\253R\255J\2652\324\253R\255H*U\251\024\324\213O\006\244\024\264QM4RRRt\244$\342\220{\322a\201\312\363M,I\344P\017\255;\214\360h\245\006\234\r<5(4\365cO\006\226\227\024\264\nPiisNSS)\245&\232M0\232J3Fj65\031j7Rn\244\335F\352\225\033\212\224\036+\314\215!8\242\212)\303\245<S\326\245Z\225jU\251\226\245Z\221jU5\"\324\202\244\006\236)\342\235\2323HNh\2434\204\322SI\244\3154\322\t\n\364\241e\301\346\234]X\360(\371{Ss\315(4\340i\340\323\305<S\251A\365\247\216zQE.i3J\017\255(8\251U\251\305\251\204\323wRg4f\226\243sQ\023M\335I\272\223u\033\252x\232\236\322W\232\346\201\315)\351H\r-8S\305<\n\221jU\251V\246Z\225MJ\265\"\232\221jQO\024\360i\340\323\263I\232;\321\232Bi3IHO\024\302i3M4\334R\216)E-:\214\323\301\251\024\323\301\247\203K\326\201\221\305(4\273\251C\003KE&j@iwSKSKR\006\247f\226\230\325\003\034Te\251\245\250\335K\272\244G\300\241\236\274\374\212J\t\245\024\264\341O\002\244\025 \251TT\252*Q\305H\246\245SR\255<T\200\342\236\r<\032x4\271\2434f\214\322g\336\214\322n\3074\326bi\224\322i3I\232PiE:\235J\0058S\205<\032x4\340psN\316\356\264\224\240f\214R\200{sN\036\364\204zP\033\024\026\246\346\220\265\000\323\324\323\211\246\267J\253!\346\241-M\335I\276\215\364\345\227\212S%p\331\244\315!4\240\342\2349\251\000\251\022\244\002\236\005H\016*E\004\367\251\000>\265\"\232\225MJ\246\244SR\003N\006\236\r?4f\215\324\002)3Fh\315!>\264\204\323M7\223M9\024f\226\224S\201\247\212p\024\340)\324\242\234)\302\235\232\0058\014\323\302\346\244\000\001H\334\216*\022\r\' \323\261\232i\024\332L\322\356\3058=\014\374UYZ\2533sM-M-I\276\220\311\203K\346W\036\335i\271\245\315-<T\212jd\000\324\300\nv\332]\264\365\251\024\323\305H\246\244V\251\025\252P\324\360\324\340\324\354\322\346\214\322n\244\315\031\2434\231\367\2439\242\233\315&\t\240\nZu(\024\341\322\236)\340\323\207ZZu--(\251\024T\200S\361M \322\021\212\215\215\013\232v3\326\243u\305Bi\271\243v)\013UyZ\253\026\246\026\244\335HZ\243g\305\n\371\025\313\223\223E\'J\003T\201\263N\rS\306\370\251\321\352]\364\006\247\n\221i\300\323\301\251\026\244\025 4\360i\340\323\263J\r)4\334\321\232L\321E(\243\036\224}h\3074c\326\224\016)\300R\342\226\234:R\212p\247\212u:\212P*t\\u\251\024sO\307\2454\203M*M\"\305\334\323\304`\016\224\205)\214\271\025VE\301\250I\346\232M4\265V\225\252\271j\214\265&\352\013Tl\324\212Nk\235u\301\244\006\226\232G4\345\034S\226\246SR\251\251T\324\200\323\301\251\001\342\224\032p5 5\"\265H\032\236\032\236\032\234\r(4\354\322\032i4QE8R\343\024\244n?-(\030\034\322\355\357J)\301sF)qK\212p\024\242\235N\002\236\005(\024\365^jQ\351R\016*D\030\0314\270\024\234f\220\221J\016)O\"\242u\300\252\317\212\251/\025\\\2651\232\240\220\346\2533TE\2517PZ\230Z\244OZ\304u\315W*E%\031\346\2274\340jE5*\232\221MJ\032\244\016)CS\203S\203T\212\325 jxjxjxjx4\340i\331\2434Rd\032>\224\242\224\032p\247t\373\264\240f\234\247\035E.=*D\311\030\244\307<\322\342\214S\200\245\3058\np\247\201N\002\244Q\3058\nxS\326\235\272\223u!jM\334\346\202\371\251#\177Z\212y;\n\250\354j\274\247+UKb\242f\250]\262\rUc\223Q\261\246\346\220\2657v[\025aH\305d\221Q\262\203Q\264~\225\021\0304\242\234\005<T\200\323\301\247\203O\rN\rO\rO\006\236\rH\r<\032\220\032x4\360i\300\323\301\245&\222\212)E(\247\212p4\341N\034\365\247\004=\251\300`\323\331r3M\242\234\0058\nu\002\244Z\221Fi\346\225E<\275FZ\224P\302\231\2323NV\3051\2335VV\305V\221\370\252\214\334\324.\325\013>\005BMFM4\232ajj\037\232\246\316*\211\031\246\021M<\036j)S\270\250\207\006\236)\300\323\201\247\003O\rN\rN\rO\rR+T\201\252@i\340\323\301\247\203R\003O\006\234\r;4f\226\212QN\035i\302\234)\302\234*@i\300R\266q\326\220R\322\212Zu(\251\026\245N\224\340(\346\2028\250\363\315.\361H\322SKSKR\027\244.1Tg\227\223U\336O\226\253\226\250\335\270\252\356\325\021zc50\2651\232\225\033\034\323\214\225X7\024\3074\314\322\365\025\t\034\320)E:\235J\r(4\340i\341\251\341\252Uj\2205<\032\2205<\032\220\032x5 4\354\321J)iE8R\216\264\361\326\234)\300\323\205<\032x\346\215\270\240\nZ)A\245\025*\324\240b\244QI\'\025\031~\324\322j6ni\244\323wTm.)\215!=*3)\031\315S\222M\315QH\374T%\351\217\'\025Y\236\241g\244-\221Q\231)\245\350\3631F\372\256\257\212\035\371\247+\002)H\300\372\324\r\301\240\002FiA\247\003N\024\341KE(4\360\325\"\265H\255R+T\200\324\200\324\212j@j@i\324\240\322\322\203J\r8S\2058S\251\300\323\201\247\203OS\212\220`\365\247l\006\243<\032(\240\032\225\rN\247\232\225M6S\232\256i\245\2526j\214\265F\317P\273\032h|S\036@{\325f\340\325y_\232\204\265E$\225]\244\250\313\324m\'\2753}&\374\232\031\251w\361\212\210\032\010\315 8\342\245\r\221H\311\270\344S\261\260u\241B\261\347\212xU\'\031\240\250\354h\002\212(\245\006\234\rH\246\244SR\253T\212\325*\265H\246\244SO\3158\034\321\232Pis\212p4\340i\300\323\301\247\003N\024\340i\340\324\213R\251\300\250\336\243\315\031\247\n\231*PjE4\217U\337\203Q\263b\242g\250\331\252\"\325\0335B\315Q3TM \025\003\2605\003\266*\273\266j\274\204\212\207\314$\323Y\351\273\351CsAl\232pj\211Z\244\315;\002\224qR)\024\222\214\257\035\2521N\025 \247\216:R\032\000\024\231\242\224\032x5\"\232\221MJ\246\244SR\251\251T\324\200\322\212u\024\240\322\203N\006\234)\324\341N\006\236\r<\032\221Z\244\335Lza\3068\244\024\3455:\036*@i\340\342\225\216EWaP\311\305Vf\346\232\315Q3Te\251\214j\007\252\316\rC\236\271\250]\372\324\005\361Lr\030Uv\030\250\230\323\013b\205|\323\225\275i\302Nh\024\240\346\236\265 \024\003@\2239\030\315\"\343w\315\300\247\034\003\3058\032x4\264\204SM&i\300\323\201\251\026\244Z\225jU\251\026\245SR)\247\322\322\321J)E<\032p4\271\247\003N\006\236\r<\032xj]\324\323I\364\247\n\224\034\npj\2205!|To \305V\221\352\2635F\317\305D\315Q3\324fJa|\323\030\344Ug\030\315Tr9\252\356\324\315\370\250\344q\332\243$\032\206F\3051_\013I\347`\322\371\340\016\265p\232\007Z\221H\247\223@\246\276\027\225<\323U\311\353R\016i\302\234\016)\300\321\232Bi(\024\365\251V\245QR-J\242\244Z\221jU\247\322\212\\\321Fi\324\341N\024\np4\271\245\rO\rO\rK\272\234\034R\202\r9y\247\223\232Pi\013\342\232d\250\231\252\027j\254[\232\215\232\230Z\240v#\245@\322Sw\344\3224\230\025RisU\267TNs\322\253\273\021Q\026\246\371\225\034\255\221\232\214\036)\214\334\324l\325\262M74\240\234\324\212r9\251\007\000\324\017\311\241x\251T\323\301\3158R\321\232)3J*E\251EH\265*\324\242\244\024\361R\003N\006\235N\242\2123N\006\236\r.h\315\024\271\245\rN\rN\335I\272\2245N\217\305H\264\342x\250\\\324d\323\031\252\031\033\212\256Z\230MD\307\024\322\303\034\325Iz\234TFP\243\336\253IrI\300\250\231\311\034\232\207q&\220\266\006*\0275\013TD\323$o\227\024\302p*&jh\311<V\306\354\323\t\251\243\373\274\323\324\323\363L#\212h\340\323\301\251\026\236)h\242\212QR\n\220T\213R\251\251EH*@i\302\234)\331\245\006\235E\024\242\234\r-\024f\2274f\2245.\3523OSS\257\"\236\246\225\215B\315L&\242f\250\035\252\026j\210\2654\265B\315P\263UIO\245W\315F\315@\340d\324,\331\311\250\213Td\323\r@\347\006\241g\315\0107\034S\300\330s\236\225\177\'4\365\251T\366\247\322\364\024v\244\305(\251\026\236)sFiiE8u\247\n\220T\212*U\251V\244\025 \247\001N\024\264\341J)\331\030\351IJ)h\242\2123K\2323@9\245\247\255N\215\305H\0174\3622*\026\025\021\025\013T/U\331\252&5\0335D\315P;TM\316j\244\200\255F\0334\331\037\013P\253dT.\374\361Q\226\244\335QHj#\3159\030(>\265\033=l\200\r4\360i\350y\251A\241\216\005,|\2574\273M \353O\006\244\007\024f\200iE:\234)\342\244Z\225jU\025*\212\221E<S\205(\247S\201\315(\245\243\351E\024Q\2323Fh\245\006\224\034S\251EJ\246\246\034\214\323\201\241\205B\343\025]\352\007<UF=j&j\214\265D\355U\334\323\013T/\363US\36256^V\241\034-B\375j3Gj\216R\002\324!\276Z\214\2754\265n\253\323\261\232p\030\251\001\247\021\305:1\201O\307\345L<\032)\331\245\006\224S\251E<T\213R\250\251TT\252*@*@*AKE(\247\np4\354\203\332\222\214\372\322\023I\2323Fh\315\024\271\247\003N\024\361O\034T\250\335\251\343\031\247\023U\345`*\243\275Ww\252\356j\0064\306\346\242cPHs\322\241-\353M&\253\3102i$\000\'\275@zsQ=ELv\300\252\222K\232a~*2\324\205\253l5L\215S)\315H\026\236\005=W\025 \034SY8\250\250\247\nu:\234)\342\244QS(\251\224T\252*@*E\024\360)\333h\305&)\302\224\nZ)\246\212L\014u\244\242\214\321\232Pi\300\323\203S\203S\301\251\024\323\303\363C>\005S\226L\232\256\355P;T,\325ZG\346\243\3631Q\273\347\2450\236*\273\2674\200\346\220\214\232c&[\223\300\250g\340qU\230\361P\261\305C/CU\030b\243&\233\232ij\334\006\244SV#j\264\234\212x\342\244Z\220\nR\274Ug\034\361M\245\025 \024\243\255<S\300\251\026\246AS\255L\242\244\002\236\005H\242\237\212LQ\266\227m\030\243\024b\223m\030\246\220;SN1M\242\214\321\232]\324\340\324\340\324\360\365\"\267\024\026\346\221\334m\252r?5\0135@\355P\263\324\016rj\007\342\242\3630y\241\233\212\205\201\355LRKb\207s\273\002\230M5\210\306\rVu\306qP7Z\205\370\252\322T\004\323\t\246\223[\240\324\212j\304f\255!\251\001\251Tb\246ZRj\263\016i1@\024\360)\300S\200\251\000\251\024T\312*e\0252\324\242\244\025\"\212~(\305&\3321K\212LQ\2121AZiZa\\\347\024\302)\244RQF}\350\335N\rN\004\324\212\330\034\323\032L\232Fo\226\252\273TL\334Uy\036\253\264\225\023I\3151\233\212\254\347\232M\365 \341y\250\207\031\367\246\232k\032\201\330\223M \221PH*\263\236*\263\234\325v<\323\t\246\223[\252je5<ue\017J\235je\247\203J\307\212\212\214R\201J\0058\nx\025\"\255H\242\245QS-L\242\245\002\244\002\236\253O\002\235\2121HE!\036\224b\226\214Q\212\n\324l\246\242a\212a\024\332C\326\233\2323N\006\246Zk\266*\271~i\314\374T\016\325\004\217U]\352\274\215U\313\234\322o4\3269\246/Z\224\266\0056\243s\212\215\232\243=i\340qPH9\252N9\301\252\257P7Z\210\232a5\274\225:\212\236:\262\274b\254!\315J)CzP\\\343\030\244\034\322\201OQN\333N\002\236\005<\n\221EJ\242\245QS(\251\224S\300\251\000\247\201N\305\030\244+M\305\030\244\245\006\226\202)\244TN9\310\250\230S\010\246\232i\024\224\245\200\245\022\340To&\352\217<\3223\361U\336LUg\222\240y*\006z\205\233&\233\272\215\331\247(\346\234\313\315\025\024\234\232\204\365\245\305/j\205\272\223T\346\0309\252\255\311\252\362pj\003Q\265t\010*\302\234T\250j\312\363V\022\236\030\032\025\261\320\323\363\221\3159O\030\247v\247(\247\201J\005H\005<\n\221EH\242\245Z\231jU\251@\247\201R\001K\212)\r%&(\305\030\243\024\021HEF\313Q\262\324ei\204S\010\246\261\305G\313R1\300\342\233\232kTL\330\252\262\2775Y\332\240f\250\331\261Q\026\346\233\236i\353SD2j}\234\234\324.1P=DFi@8\305(\004\214\nc\256\336\265NnsU\010\353U$\250MF\325\321\"\032\231S5*\200*\302\234\nw\231\330S\303\032r\232\225NjAO\024\361O\024\360*@)\341i\340T\212*E\025*\212\231jU\025 \024\3608\245\"\223\024Q\212m\024QK\232Ji\024\302\276\225\031J\214\2550\255DS&\221\206\321Q\221\232\002b\230\342\252\313T\344\252\256j\022\365\013\276M34\n\231*\314?xU\2228&\253\311\336\25352\227\034qD|\212\216j\243%@zU\031\206\r@M1\253\250\351J[\002\205j\231Z\245S\353S)\030\251\027\232\221V\244U\251\025\rJ\261\232\221b&\236#5 B)\352)\340T\200\n\221EJ\253R*\324\200\021\326\236\265 \024QHE%!\024\224QKIE4\212i\024\302\275\351\205i\206<sP\310\274\323U;\320\303\002\240\220\3259\0175NS\212\246\3475]\215DM%*\324\351V\242\030\"\255\0221Ue\252\315M\357OQH\027nj\031\252\224\225]\370\252\322\256j\243\014\032\214\327LOzhl\232x52\232\225MJ\2652\232\235MJ\246\246Z\225EL\2652\212xZxZ\\{P\024}*@\247\352)\340\343\255L\2305(\024\270\245\315\031\245\242\220\212Ji\030\242\212L\321\2323IHi1\232k\014Tl{T/M\350)\215Ue5Q\352\224\334\232\252\374T\014)\204S)\351S\'QV\242\346\245|\214\021P?5\023-DF\rH\2074\3622*\244\243\004\212\251(\346\252\311P\236j\244\313\203U\315tE\363J\265*\212\231jU\034\324\312*eZ\231T\324\310\246\247U5:!\251UjP)\342\236>\224\341N\306h\000\216\224\340\304u\251\025\324\366\305J\030v4\241\251w\212p#\265-&)(\244\'4\224\021\232m\024\231\245\315&x\244\007\0249/\315DEDW&\220\214TRUIj\243\203T\344\035j\263\212\205\2051\2050\255*\212\235\005X\214\342\236_\327\221Q1\007\245\'Z\214\216x\247\001\265M&x\252\322\034\346\251\310j\273\212\200\214T2.\341U$\\V\332\324\253S(\251\224T\313S\245N\265:T\350*t\025:\212x\031\247\201N\002\236)qJ)\334\322\322\201N\024\365\315H\006i\333\005.\334t\244\311\024\231\245\315\024\322)\264QHi3I\232:\322\206\300#\035j3M\353LqU\336\253H3U\334dUI\022\252\310\265\\\212\217\0314\2453@J\231F\005<u\247\3435\013\251C\354h\246\363\221J[\203M\317\006\253Jj\253\014\324.*\"\264\306Z\253*f\264\326\245Z\231jU5:\324\351V\022\247AS\240\253\010*e\247\212x\247\016\224\372p\024\340)\300R\205\247\205\245\013O\013O\002\235\212Z\nR\025\244\331\355M \212NM!\024\332)\247\336\232i@&\244T\241\222\233\345\226\031\364\2462\355\250\330T\016\271\250\031*\273\307U\244N*\244\211U\236:\214G\315?e\033iqKO\007\212d\274\212\214PG\024\303L\317\006\240~j&\025\003\212\205\2054\212\201\305\\SR\251\251\222\246J\235*t\253\tV\022\247J\260\225*\324\213N\024\360)\340S\300\247\201N\305(\024\340)\340S\261O\002\234\026\245H\367\032\227\311\002\232\321q\3050\307\216\325\033% \212\220\307\352*6OJn\332c\n`\0252-HN\0054\363H\006\r1\223\'\232\215\223\025\031AQ2\n\255\"\325YW\345\252\016*\026\031\244)\307\024\230\246\342\232x\240\034\322\366\246\236\234\322\001JG\025\021\025\031\351L+Q:b\253\270\252\357L\353Q\270\251\301\251\226\246SS-N\225a*\302U\204\253\tV\026\244Z\221i\340S\305H*@)\352)\370\243\024\340)\300S\300\247\252\324\251\036MXU\300\247\342\232E4\212\214\2504m\024\233i\245\001\246\030\207j\201\3415\037\226\300\323\324\021\326\220\344\232r\255;\024\240T.\274\324\0141P\260\252\356\265^Q\305Q\2219\250\n\321\212iZ\215\306*#J\253KH\302\231F\354\212k\n\214\n1\326\242q\315V\225j\233\216i\240b\243z\225MJ\246\247J\231*\302T\351VR\254%YJ\231jU\251\001\251\026\236\242\236:T\252*@;\323\326\227\024\340)\301jEBzU\210\340<f\247\010\007\002\234\022\224\2454\212a\024\314R\n\r7\255%\004\003M()\245)\246:M\270\246\232BqL$w\353Q8\025\013\255V\222\2538\315U\221sP4t\2051L+Q8\3153e.\321MaM9\"\230E ZR\271\024\2018\244+\324T2.*\254\235\rTu\346\242#\025\023sOJ\231jt5:T\350*\312\n\260\234U\2048\251\325\205J\257R\253T\200\324\212jE5*\232\221MJ\265 \366\024\242\236\242\245U\315[\202<\014\232\225\260\243\212\022\246\002\224\364\250\030\363Q\223LcM\3154\232L\323wS\201\342\214\321\232ajN\325\031\036\224\326\250\315F\325\013\232\257%VcQ5Bi\244f\230\313Q\224\246\221L\"\230E0\214\322\021H\005=W=\2516\342\232G4\307\213p\342\251I\0363\232\253 \252\354)\205i\026\245SS\245X@j\302\n\262\203\0252\324\352jU5*\232\225MJ\246\245SR\255J\265\"\324\252}j@\376\224\345\253\021\256j\312 \0258\245a\220)@\305.\354P[\212\211\215Fi\246\232i\246\232M0\365\247\003JM74\003\203\315)!\272P1QH3\322\240\"\230\302\241aP8\250\031j\007J\205\201\024\332\017Ja\031\250\330TdTl9\240-!\024\3209\251T`R\021L+\221M?v\252\3123\232\246\351\315B\321\346\242d\250\026\246A\232\261\032\325\244\025:T\313S-J\246\245SR\251\251\226\245Z\231jU\251TT\252)\364\340*E\025n\016\225d\032z\232\225H&\236j2)\246\2434\001M4\302)\270\342\230E4\212i\310\240\236)\205\216i\300\322\346\214\323\032\242jcT-\315B\3035\023\n\211\226\241d\250\312R\025\246\025\2462\346\243\"\231\262\224\255F\303\024*\323\310\307Zn3F*\'\\\016*\273\256E@\361\324-\035D\351Y\351VPU\210\352\302\324\252jUj\221Z\246SR\251\251\224\324\313S\240\251\324T\312\2652\212x\251\000\247\205\251\000\253p\217\226\246\013O\002\234\016\r.\352\\\323MFi\r\'ji\246PE0\323M0\323\010\244\316)CS\263\3055\215Fj&\250\332\243j\215\206j\"*2)\245)\245)\214\225\023%0\245\0331Me\250\331h\013M#\'\232\\\036\324\214\204\014\323\010\312\234\325r1Q\260\317Z\211\226\240u\315e\240\251\320U\204\310\251\224\324\213R\255L\246\245SS\245N\225:\n\235\005XAS\250\251Ui\341jEZ\220\n\225S5f5\300\346\246\006\2349\245a\3050\032\\\322\023L4\207\245(\2468\364\250\311\246\026\246\226\2439\246\236)\206\232E\002\234\r\006\230\334T/Q\036\264\323Q\232k\014\324eh\tF\332c\257\245B\313L+M+\212a\025\031\034\323H\246\323\227\255\0142)\254>^\225Y\205D\302\241qU\333\203Y\310\2652\216\225*\324\253R-L\265*\324\310*\302\n\260\202\254 \253\010\265a\026\254\"\325\210\342-\322\245\3733c4\2331OU\251Pb\254\257LR\201\315H\242\211\016\005C\272\227u4\232J)E-0\250\250\331*2\224\241i\214)\230\240\212LR\205\240\212\215\252&\025\031\024\302)\204SqM+J\005!\024\3223P\262\323\n\342\232EF\313Q\221L\"\232V\2000i\3148\342\230\303+U\330Tl*\t\005Wu\254\325\034T\253R-L\265*\212\231EL\202\247AV\021j\302-YE\253(\265a\026\254*\325\210\320\366\253pr\n\277J\212E\033\270\024\005\247\201R\3069\251v\363OQQK\311\250H9\245\353IFh\315\000\323\201\243\024\230\315!Za\025\033-7m&(\305=S\"\221\226\242e\250\331j&Z\211\205F\302\233K\214\212\000\241\205FE4\212\215\226\243\"\232ED\313L+M\"\220\214P\t9\305\005p\rVa\315F\303\025\013\014\324N\225\222\005=EJ\265*\212\231EL\202\254\"\325\204Z\263\032\325\224J\262\211V\021jtZ\2361\310\315\\\014\252\240/Z\025\210<Q\214\320F)sO\214\363V\200\340\032x\030\025\013/4\322\242\230V\232E!\244\242\200i\342\236\006h\333\315!L\324n\225\036\312iZLsR\240\244aP\260\250\210\250\310\250\312S\031*\"\224\230\305\000\320Ni\244SJ\323\010\250\330S\010\250\310\2462\323\010\244\333\232ENi_\345\004Ub*\026\246c\232\032<\212\300\006\244Q\232\225EL\202\247AS\240\253(*\302\n\263\032\325\250\326\254\242\324\352*U\0252T\242\244\024\374\322\023\232J\2325\357V\024\361O\3154\256i\245i\204S\010\246R\021\2121\305 \034\324\2128\247S\363\221F)\031A\250\331*2\224\302\264\345\024\244TL*\026\024\322(\331\232C\021#\245E$[G5Y\206)\270\315\030\244#4b\243aQ\232a\024\322)\2148\246\025\246\221GN\225\013\222MD\302\243+M\013\3159\206\005sj*U\0252\212\231EL\202\247AVPU\230\305Z\214U\250\326\254\242\324\312\264\361R\251\247\203O\006\235\272\2234\344<\325\264\344\014T\341x\245\333N\333Me\250\210\2460\250\332\243&\223u\000\346\236\r<\032}8R\342\223m#%DR\200\230\246\260\250\331j&\024\300\265,i\223S\004\030\306*\033\230\376Z\314t\346\243\306(\245\333I\214Tl*6Zf)\244S\010\246\221M)\221Q\343\002\243aQ\260\250\312\321\216i\262g\025\316\250\251Uje\0252\212\231V\247AVPU\230\305[\214U\250\305YQR\n3O\006\236\r<S\351qOU\253p\203\212\264\2434\340\224\3420*6\025\021\025\023\n\214\212c%0\255\030\305(4\365\251\001\247\346\234\r8\n\220 \"\243h\361L)Q2\324n1P\232@*P=)\335\2529\016\340A\2522\2475]\3053\024\365\031\244e\250\210\250\310\246\221I\267\"\230\313L\305\0140*\006\246\021Q\260\246\204\'\240\245\331\216\265\004\247\002\260UjUZ\225V\246U\251\325jdZ\262\213VcZ\265\030\253IS)\247R\212z\212\221EH\005<\n\221V\246T\251\223\345\251C`\325\205pE\014)\245i\214\265\013-D\302\233\212B\264\322\264\3221FiC\322\371\225*6je5 jBsHF*\'\"\240a\232\211\226\243\350jT4\346\2467J\255(\252\222\n\212\244Z\030TL\265\031\024\303I\315!\024\322\264\306\034T\004S\010\246\225\245\215y\311\244\223\212\245)\306k\031V\246U\251UjeZ\231\026\247E\253\010\265e\026\254\245N\265*\323\300\247\205\247\201R-H\242\244QS\242\324\341qMn\r9[5*f\247\316E(\244a\221Q2f\241d\3057m\033i\245j\027Z\214\323y\240\023R+\021R\254\206\245\022R\031h2f\230Ni\206\230\302\230V\234\274S\310\342\242j\201\352\273\214\324%piV\202i\206\230\302\243\"\223\024b\232\303\212\215\272T$R\005\243\313\356hn\007\025VJ\25175\230\253R\252\324\312\2652-N\213S\"\324\350*\302\n\235EL\202\247QR\205\251\002\323\302S\202\323\300\251TT\351\305L9\244d&\205\201\210\310\025*#\016\2650\024\243\203A\2445\023.i6Q\345\323\031*\026CP\262Rm\246\221\3158-/AFx\244\0074\271\245\245\353\326\202\264\322\264\233i\370\342\241a\315B\300TEj\007^j3M&\233\232\t\246\342\232E&)\2568\250H\246\021@\024\343\322\243qU\234UIEg\252\324\252\2652\255N\253S*\324\352\2252%N\253S\242\324\312\265:-L\253R\252\323\302\322\343\332\200*U\025*\212\261\027$U\305\010\247\347\0314\327\230.Dc\203O\211\303\2140\0242m84\321\212\221c\315+[\236\324\337 \322\371\036\324\206,Tm\035@\311P\264u\013Fi\233M\024\322i\t\244\315(jx4\341J)i\244\ni<TOQ0\250\332\243e\252\3561Q\323H\240f\214\342\221\2104\001L\177AQc4\322\224m\305\014*&\025\003\214\325i\022\263\324f\246U\251\225*tJ\235\022\247T\251\325*eJ\231\022\254\"T\312\225(JxZp\024\355\264\241jEZ\224-H\274T\200\223R\004\006\244H\361Sc\214\036}\352&C\236*xP\236\247\025q\023\324\212\220\306;\n\215\242\250Z:\211\343\250Z:\211\243\250\031=\252&\2175\023\307\212\205\226\230E&)@\247\250\251\002\323\200\3055\251\271\244\250\332\243aQ\221\3155\207\025\004\211\232\204\256)\207\2321M\"\224-+\014\n\204\212n\332wjk\nc\n\211\206j&\025^E\254\364J\235\026\247D\251\321*tZ\235\022\247D\251\321*uJ\235\022\247T\251\002g\245)\214\257ZP\264\270\245\002\245QR*\324\201i\301i\342\245V\305<\267\245\033\260*H\334\032\2345H\256A\342\247\014\030r)\214\202\241h\352&J\205\343\252\354\224\302\225\023\307U\335*\022\264\335\264\340\265\"\2558\322c4\322\270\250\317\024\335\324\204\346\232V\242e\3050\323\030f\242t\364\250J\320E7\024\360\000\246\260\334*=\264\355\235\351\254)\245j&\025\023\212\205\215B\346\251*T\350\265a\022\247T\251\321*\302%N\211V\021*tJ\231R\245\tR \301\342\234\371f\344Rb\223m(Z\225EL\213\232\235c\315N\220\257\361\032kE\203\305FW\024\240\361Mf8\250\304\205\032\255G85:\316\007z\221nTT\242ezL\212i\025\033.j&J\215\243\025\013GU\244J\252\353\212\217\024\341\232\224\003F(4\306\250\332\243\"\201\326\244\n\rG$uY\306*2\336\264\322\300\212\205\210\246f\216\364\356\270\024\366A\267\212lj6\234\323\037\212\214\323I\246\032\205\305V\220Uv8\250\343Z\260\211V\021*\302%N\211V\021*\302GS\242T\312\2252\245J\022\236\251\203\234T\205\001\034\365\246,T\246:M\264\340\2652\014T\352jU\004\364\247b\232S4y&\230\321b\243h\201\246\204\333FqNQ\223\315Y\215\200\351S\357\243}\005\263M&\230\303=\252\026\025\004\213\236\325\t\267\334y\240Z\001A\267\307Ji\217\024\335\224\205*2\264\306J\205\2050\361M/\216E\006\347\214\032\247<\343\265Q{\203\353Q\371\344\3654\276a=\352T9\251vq@\0304\374\346\225z\232c\255B\303\025\031\250\311\250\335\252\274\225U\372\323\343Z\262\213\232\260\211V\021*\312%XD\253\010\2252%N\251R\252T\312\224\361\035/\227JW\035)\273sF\312P\230\247\001\212\220T\310\330\251G\315\322\230\310A\245\010\330\344\322\265\273c \346\243\020\261\353R\01029\024\306\266\3054B\001\346\244X\300\350jQ\037\024\306LS1\351F\342(\363=\251\013\2554\225\365\244\033OJB\0054\201Q\262f\231\262\232\313Q\354\315C7\313\300\252\304\346\243~*\244\262b\251\311>*\263;9\342\230\310\330\252\316\305\r\t6MZ\216J\271\034\231\024\245\250\006\244\003\220hqP0\250\230T\rQ1\250\034\325g\025b1V\343Z\262\211VQ*\312%XD\253\010\225:%L\251S*T\213\036j@\224\375\224\236^iD4\030\351\276]\'\227J#5\"\241\025*pjl\202*\t\t\3155e`q\232\23599\315XQK\260\032cB\rG\345c\245(CC\naQ\351Q\262S\n\232aZaZiZL\021J3\336\227\024\230\246\262g\2655W\373\302\251\\\217\234\342\252\234\324o\310\254\371\301\346\251\030\231\317\025n\336\323\035EZ6c\035+.\356\307\004\220+5\241dj\221\033\025f)*\312\266i\300\346\254 \312\212V\217#5\013&*)\026\252H*\273T\rP\275[\214U\270\305[\215j\324kV\221*\302%XD\251\321*eJ\231R\244\tR\004\247\204\247\010\363N1\2208\024\303\037\255\001\005#(\355H\006(\357J)\313\326\207L\324b0O&\254\306\212\243\203\223R\201N4\337\251\240\342\231\232Ni\254*\")3\232FL\364\250\331qL\"\233\212\220 \3051\227\024\231\2434\202\241\232\000\340\234sY\322&\302EU\220\342\241\333\270T\220\300\255\332\256\307\000\003\201S\030A\035*\254\366\201\201\342\261/l\366\202@\252?g;rE5\020\206\255(-L\213\220y\241\340h\31755\271\347\006\247t#\351P2\324\022-T\221j\254\202\253?Z\201\315_\214U\270\326\255\306*\344B\255F=*\324kV\021j\302%N\251R\252T\201)\352\231\251U)\341y\241\200\355Le\246\021\212n)6f\202\270\246\322n\"\233\274\232o9\251\341<\363V\326\224\212L\n\215\200=)\002\232\017\024\322\302\230j2)\271\"\232Nz\323)\010\346\246P1Mq\223Q\342\223m.\312\n\341O\025\233p\233\311\300\252o\006:\324\"\"3V \214\203W\221x\247\221Q\262\346\252Ml$\353U\215\222\200F*\224\226![\345\025b\010\031\007\024\351>a\206\034\325`\233_\"\254\261$sP=@\342\253H\265NQU$\030\315U\220\326\254kV\343\025j1V\343\025j1V\243\253Q\212\262\202\254\"\346\246U\251U*UJ\220 \240\2554\2554\2550\2550\256\r7\034\321\203M\"\230V\223e(\2175,i\203V@\247m\3155\206*<sA\351\311\250\330\366\024\334f\215\206\215\206\230\313P\225\246\221I\212\221\006N)XsM\333N\tK\267\035i\030\006\025VH\324sT\245\000\232j\302\032\245\020\340\323\300\305.(\3050\256j3\0350\302\t\311\024\024\000t\252\362\301\225$U=\234\363O\"\242a\305WqP8\252\262\255P\230U9\005l\306*\324b\255F*\334b\255F*\312\n\264\225e*\302\n\260\202\247QR\005\247\001A\024\204Sv\323v\323YsL)I\262\220\245FiUsR\025\013J\204T\200\212]\300R3\203Q4\212:TM \365\246\207\006\234$\002\227\315\364\244\336M!\031\246\225\024\323\036j2\244R\253m4\343\311\315(\245\310\025\013K\223@\220c\025^c\232\250\312I\253\020\307\305LS\212a\\\016)\204RSM\024\322)\244TdsPK\010\373\302\241)\305@\353P2\324.\265ZE\310\252\023%R\221+^1V\243\025n1V\243\025f1V\220U\204\253\021\212\264\202\254\240\251\324T\240R\342\214R\021I\266\220\2554\214Q\2674\322\224\306\035\251\236^i\215\307J\211\331\2155d\"\236\327\001G\0315\013]\023\320SE\303w\244iKv\241\010\357R\0021F\352PsOZ\220\014\323YiB\346\225\320\001\223U\037\031\371i\013\343\251\246\231\207\255Ff\31579\247\n\n\347\265\036Ni\352\233zS\261H@\250\230\n\215\2522Nx\244\335FsHi\206\232j7@j\264\221\343\245Wt\250\035}j\264\211T\345\216\251\274y\257\377\331"
+byte_png: "\211PNG\r\n\032\n\000\000\000\rIHDR\000\000\002\000\000\000\002\000\010\000\000\000\000\321\023\213&\000\000\004\255IDATx^\355\335\321\226\2338\014\000\320\036\372\377\237\2749{\232m\272\035H\006BlcI\367>2\t\030ld\311$\231\037?xfYo\000\000\000`\210\217\353\261\345\343=\300\264n\353\rl\010\001\000\300\347E\005\000\000\000\031<\312Ck\353\000\000\000\323\363\204\017\020\t\3302&h\303H\342\210\233g\t\000\000\300d\324\263W\370\2738\324\003\000\000\000@0\341\2273\302\237\000\000\000\000\260\021\355\307\342\2075\3277\030\000\000\200\234\326eU\344\352g}.\ru\334\365\305\362\236\031\000\000\000\007(\013y)\362\"\021p\206)\201\277\231\005\240\004\241\237\223\226\237\353-\320U\343h\325xw\034\"\273\004\000\000`\237\352\221.\014,\200\227\254\227\003\000@1\212\000\000\000\n\363\330\020\000\200\272,\016\003p\221\321\0132O\247\274\345K;n\267\321\215\002.\36642\000\000P\213J\020\000\306\2122\367Fi\'=X6\204h\304l\310\313\375\235\314\3414\353\360\013\001`\2079\005\340MY\002\347\222\345D \367\037\000\000@2\236\333G3\2602\367\273\014\274\305x\201\310\334\301\000\014a\302Ii`\225JSz\016\000\200\246$\230\305\031\000\000\025\025X\360Mv\212M\347\353\246;\003\372s\323\206\240\233\000\n\032Tt\014:\014\2632\000\000\000\000\240\252\343\217\036\254\037\000\000@\025\235\263\377\316\273\347s\307+\305b:]\230N\273\2459=\005\000@8\007+p\271nZ;\377;\360\373\277\002\344\'\016\262og2\005 \253I\342\377\301\262~>a\033\036\322m\226\361\n\220B\2429\314\364\000@\017\346\027\222J\224\005\002\000\345\311l\000\302\020\262y\305\032\034\000\000\364%\347&\224\027\013\010}\307\361\355\353Q_\264\001\250\304\267l+\373\325\371\006\000\000\344\243\324\203\000$\342\000\360\006k\230\000\033\277\213\177\0012\000\235\004\225\211\000\000\000\344\341\203\010\331\254z\364Q\276\334\326\3376\340\177\313\372\252Q\315\310\001`I\001\000\270\222\\\204\211\030\216\343\214,y\016\233\262Q\326N\200\234\3046\000\210\310\014\376\233\013\221\307\234KAs\266\n\262\021\313\001\000\230\205\334t\270 \227<H3\001\000\000\346\263}\340\252\304\002\250\314\017\214\027\267\374\\o\001\000\312\220\010\002\000\000q\251h\342+\327\207\345Nx\207\353\001@j&:\200Wr\177b9\367\331\3255\240_\007\034\3423\235s\233\315\3567\033\022\230\276\223G\312\330\301\234g<\000\000$%\321#\261k\213\374k\217~R\310FS\324}\3762\211\001\300\365\244\220\314l|\2768\376\210@\024\342\003\000\000\314\302z\026\000\000\000@\024Vr\000\000\000\000\000\000\000\340n\211\366]\345h\355\005\000>g\376\207\366\334WD\341s\337\245\351\376\016\"\305\177\003\240\270\267\007@\244\321\3153\341\026\252\001\242y{n\005\030\357v}\254\272\276\005\000\000\000\000\000@X>\376\002P\234\'\316\347\334\\:\000h\313\314\n\000\205\230\370\001\000\200\231\004\371\350\214R\252\270\t\276\316\016\000\000\000\300\033,\347\0009\210f$\026\344!!\000\320\310\005\037\274\270\340\220\034!\017\344\356\327\r\372\317z#\005\374\t\315\213E\017(h\221\007@I\2339\177\263!\006\021\254\021\027\222\347\202F\006\336%\004\024g\000\000\000\360\214<\021\000\200\324<\002I*Q\307\216/\312\336:\342[/&\212D7\020\224&DCY\207\277\010\262\010\024I\035\034\000\000u\325\230\001\217\235\345\261W]\250m\276b\216\004&\3264\336\001\334\265M\245\276!\313\232\323\260\001\300\\\222\335\220\311N\347\205\0067\353%\027\252\347\017\0076\270&\000\325\010\235\000\274\3201q\'\262O\006\306\355\243w\037\361\310ld8g=z\250wO}\357K\377\035\353\314c\257b\353\332\256\006h\313l\000@\006r\364}\3679\337\205:)\370\205\013\336|`\000q\002\000\370\236\222\362S\255\027\342?\351\214\263\357=\373>\272h:\244\364mu{\303\311\010\231\220N\001\372\370oJ\330\233\030\200\224\"\244\027O\303S\204\206\267\223\366l\307\235\330\270#\r\326\363\267\007\000\000\000\370c\306\352\353\374\217^.\347\337\032\315\214\035\007\227+\023\001\000\000\330\220\013\326\266\314\264 0OKz\362<\037\000\340b\023\344c\0234\001\362\333/\276j\024\241;\016\\\204\307K\376\005s\343\23463\025f\327\000\000\000\000IEND\256B`\202"
diff --git a/core/res/geoid_height_map_assets/tile-3.textpb b/core/res/geoid_height_map_assets/tile-3.textpb
new file mode 100644
index 000000000000..486adf4a5842
--- /dev/null
+++ b/core/res/geoid_height_map_assets/tile-3.textpb
@@ -0,0 +1,3 @@
+tile_key: "3"
+byte_jpeg: "\377\330\377\340\000\020JFIF\000\001\002\000\000\001\000\001\000\000\377\333\000C\000\004\003\003\004\003\003\004\004\003\004\005\004\004\005\006\n\007\006\006\006\006\r\t\n\010\n\017\r\020\020\017\r\017\016\021\023\030\024\021\022\027\022\016\017\025\034\025\027\031\031\033\033\033\020\024\035\037\035\032\037\030\032\033\032\377\300\000\013\010\002\000\002\000\001\001\021\000\377\304\000\037\000\000\001\005\001\001\001\001\001\001\000\000\000\000\000\000\000\000\001\002\003\004\005\006\007\010\t\n\013\377\304\000\265\020\000\002\001\003\003\002\004\003\005\005\004\004\000\000\001}\001\002\003\000\004\021\005\022!1A\006\023Qa\007\"q\0242\201\221\241\010#B\261\301\025R\321\360$3br\202\t\n\026\027\030\031\032%&\'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz\203\204\205\206\207\210\211\212\222\223\224\225\226\227\230\231\232\242\243\244\245\246\247\250\251\252\262\263\264\265\266\267\270\271\272\302\303\304\305\306\307\310\311\312\322\323\324\325\326\327\330\331\332\341\342\343\344\345\346\347\350\351\352\361\362\363\364\365\366\367\370\371\372\377\332\000\010\001\001\000\000?\000\364/\245.\352pz\225d\247\356\3151\316j65\0314\302i\245\251\244\323sFh\315&h\315\004\323I\246\223M\335HZ\232M4\232ija4\302i\204\324d\324d\323\t\250\311\246\023Q\223Q\263TL\325\0235D\315Q3T,\325\0135D\315Q\263T,\325\0235D\315Q\263Te\251\205\251\244\323I\244\315&h\315\031\242\2274\240\323\251\300\323\201\247\003R\242\223Vb\212\264!\213\025\353E\251\245\250\017O\017R\007\247\026\030\037Zk\032\214\232\215\215FM74\233\2513K\232L\321\2323M&\232Z\230Z\233\272\220\2654\232ijijaj\215\215FZ\230Z\243&\243-L-Q\263TL\325\0235D\315Q3T,\325\0235B\315Q3TL\325\0235D\315Q\263TE\251\205\251\204\323I\244\315&i3Fh\315.isJ\r;4\340i\302\245E\315[\215*\364H\000\253I\355^\231\276\223}\033\351\341\351\341\351\301\363O\334)\215Q1\250\211\244\315&h\315\031\2434f\220\232ijajaji4\335\324\026\246\026\246\026\246\026\246\026\250\331\2522\324\302\325\0335F\315Q\263TL\325\0235D\315P\263TL\325\0135D\315P\263TL\325\0335D\315Q\263Td\323\t\246\223M&\2234\204\322f\214\322\356\2434\240\322\203O\024\361R\242\022j\334q\364\253\221\307\264d\325\204\253q-z\000z7\322\356\245\017N\017OW\251\003\346\235\2735\033TML\315\031\2434f\214\323sHZ\230M0\2654\2650\265!j7S\031\2522\325\031jaj\214\265F\315L/Q\227\250\331\252&z\215\236\242g\250Y\352&j\211\232\241f\250Y\252&j\211\232\243f\250\213S\013S\t\246\223M&\232M7u!jM\324\233\251A4\360\rH\020\323\304u\"\305S\254F\254\305\016:\325\250\323\025co\002\245\215*\344K\212\355\003\346\215\324o\305(\222\234\036\234\036\244Y*P\371\240\267\025\0214\302i7R\346\214\322\023M&\232Z\230Z\230Z\230Z\232Z\223u\000\323\034\324L\325\0335FZ\243/Lf\250\231\2526z\215\236\242g\250\331\252&z\211\236\242g\250Y\252&j\205\232\242f\250\231\2526j\214\2654\2650\2650\2754\2653u\031\247\005&\234#8\351J#5\"\307\355R\244C\275N\261\217J\231b\036\225*\305R\004\002\244QSF9\253\210\231\253\021\307V\022:\350\326Jxz\013R\007\247\007\247o\251\025\352U\222\244\017\221\3150\236)\204\323wR\206\245\335HZ\232Z\230M0\2650\232ajijM\324\233\261HZ\240s\212\211\232\243-Q\263Te\2526j\211\232\243g\250\231\3526z\211\236\242g\250\231\352\026z\211\232\242f\250\331\252&j\214\265F_\024\302\365\031~i\013f\232h\000\232\235\"\365\253\t\037\240\251<\272M\230\355O\013OU\315J\253\212\235G\025(\034RT\252*\304K\315^\215j\312%YE\2550\324\360\324\355\324\205\2517\323\303\324\212\365 zx\222\234\030\021McL-\315\033\250\335HZ\220\2650\2654\2650\2650\2654\2654\265!jaj\215\232\240f\305F\315Q\226\250\331\252&z\215\232\242f\250\331\252&j\215\232\242f\250Y\252&j\211\232\243f\250\231\2526lT,\377\000\205F\\w\246\347=)*EJR\242\204Q\232\262\007\313V\020p)\370\243m(Jz\256\rJ\027=*@\265 \024\273y\251\221j\314C\025v<U\250\326\254*\325\260\330\245\337K\276\221\244\246\357\247\253\324\241\351\341\351\302Jz\311N\337\236\264\306<\323wQ\272\215\324\322\324\322\324\322\324\302\324\322\324\302\324\322\324\322\324\302\365\0335B\315Q3\324l\325\031j\211\232\243f\250\231\2526z\211\236\242g\250\231\252&j\211\232\243-Q\263TL\330\250X\236\246\242f\246S\200\245\035j`@\024\335\324/Z\234\036\225j#\225\251@\247\201O\013N\333O\214`\324\333)\300S\202\232\231\026\254\"\036\325b4j\271\0225[D\251G4\326\342\243/Az@\364\340\365\"\311O\017K\346S\326J\225d\343\024\026\244&\233\272\215\324\205\251\013S\013SKS\013S\013SKS\013Tl\365\033=D\317Q3\324e\3522\324\306j\205\232\242f\250\231\3526z\211\232\242f\250\231\2526j\214\2651\232\241rs\234\324li\206\223\034R\321N\240T\2129\253\n\234f\255B\277(\251\302\323\302\323\202\324\252\225(\216\236\026\244T\251\026*\263\025\271n\325r8\002\216je\n;T\311\364\253Q\256j0})\254j\00684\335\324n\245\335N\rR\007\247n\245\337OY)\341\363\212v\352Bi7Rn\246\226\246\226\246\026\246\226\246\026\246\226\250\331\352&z\211\236\243g\250\331\352&z\214\276)\245\352\'j\205\232\242f\250\231\2526j\215\232\230A=x\250\233m0\342\230Xv\024\302F*&\344\323H\243\030\242\212QR(\251Q7\036*\340N\000\253\010\230\002\246U\251\002\324\212\225*\245J\022\236#\251\243\2135j8=j\342\"\250\342\2022i\311\035Y\215*\324Q\346\250\253qC6*\t\rE\272\200\324\340iA\247\206\247\006\243u(|S\325\352@\371\247o\365\246\356\315!8\246\226\246\026\246\226\246\026\246\026\246\027\250\331\352&z\205\236\243g\250\231\3526z\215\236\233\277\024\326z\205\232\241f\367\250\213{\323I\035\3114\302\303\265F\315Q\026\246\223Q\261\246\3474\206\220\2121I\2121N\002\245E\315[\202*\274\261`\n\225R\244\013\212z\256juJ\220-J\251R\252U\204\001G\275N\274\324\240b\225FMX\2162{U\350\255\275j\354p\001\332\271\250\344\342\234NEA#qP\226\240=<58\032pjvh\335F\352]\330\247\253\323\374\312R\340\323Kb\232Z\232Z\230Z\230Z\230^\242g\250\231\352&z\211\236\2432TL\365\031zc=3}1\244\250Z^j6\222\241g\246\357\365\246\226\246\026\246\026\240\364\250\311\244\315-\006\233E\030\247\250\253Q%^\211qV\224t\251\200\245\013\223\305N\211\212\231S5 \216\244T\253\010\234T\211\036MXX\352d\2075a QV\243\214/AV\343Bj\322%p\361\276j\300<T,x5Y\233\006\2205<=H\032\227u;u\033\251CR\356\245\335K\272\215\324y\224\205\275)\205\251\245\3526z\215\236\241g\250\231\352&z\210\275F\317Q\227\250\331\3522\364\322\331\250\331\261P\227\250\231\371\246\026\246\227\246\027\246\227\244\335Jd\342\233\232)\331\242\214R\221@\031\251\243J\273\nU\245\\T\350*`8\247\242\022j\334QU\205JxJ\221c\251\3250*x\343\346\254\254u:ES\244&\255G\020\035j\302(\035*\312-y\324G\275YS\221Ls\214\325)\033\006\232\032\234\032\236\257R\256[\245\004\225\353F\374\322\206\245\rK\272\227u\033\251\013\323K\323K\323\031\352&z\215\244\250\231\352\026z\211\244\250\331\3522\365\031z\215\236\243/F\372\211\3335\013=F\315\3150\2650\2654\2654\265&\352]\324\341\203E8\032x\245\245\0035 LT\211\326\257CVUsV#J\262\261\324\311\035N\243\0252.j\312G\232\220%H\251V#\216\255\"U\204CV\0251R*\223V#\216\254\252W\230F\370\253\010\364\216sT\346\353\232\213u(jpz\265o2\251\371\205\027\022\2536W\212\204?\255H\036\227w\245\001\350\337AzizB\364\306z\215\244\250\232J\215\244\250ZJ\211\244\250\232J\214\311Q\263\324e\3526\222\230^\200\364\307z\201\236\230Z\230Z\232Z\232Z\2234\231\2405<58\032x\247\216i\336\302\246ARb\205\034\325\270Z\257C\315\\\215j\312\255<\014T\321\256j\334q\364\253J\234S\325*t\216\254$uj8\352\312GR\210\352DJ\260\211V\021+\307\204\236\2254sv\251w\346\240\224\344Um\324n\247\006\245\337H\\\232\262>h2G#\275F\262T\233\3517\373\322\356\244\337H^\230^\243g\250\232J\211\236\242i*&\222\242i*#%F\322TfJa\222\243/M/F\372c\265D\317Q\226\246\226\244\335F\352L\323I\240\032x5 5\"\342\247T\317\265.0jt\034S\261OU\251\343\\U\330[\025~#\320\325\201\315J\213\232\271\014|U\330\243\342\247\tR\244y\253\t\035X\216*\260\221\340\325\224J\224%H\211S\204\251\343Z\360\245\233wCS$\2305i\037\"\232\346\2539\301\246\346\227u;u&\354\032\235g%v\346\231\300\357K\276\215\343\265;x\355I\276\232^\2432Tm%B\322TM%B\322TM%D\322Tm%F\322Tm%Fd\2442SK\320\036\232\357\305D^\233\232ijM\324f\2279\244&\220\032z\232\225ML\206\254\253qJ\0075*\n\231V\245T\253\010\265e\022\255\302z\003WQ3\322\255D\225r$\253h*tL\325\204\216\254\244uj8\352c\037\024\344\030\251\325jdZ\231R\247H\353\347tl\032\260\222f\254\306\346\245c\300\250d\342\242\335\2127S\203PZ\233\272\245^A\244\335I\276\223\314\305\036e4\311Q\264\225\023IP\264\225\013KQ4\225\023IQ\231*&\222\243i*6\222\230d\246\231i<\332x|\323Y\252-\324o\2434\204\321\232\\\321\326\201\326\236*U\251\226\254%J\242\246QS\240\253\n\2654iV\321x\251\222>j\334y\025v\023\232\277\032f\255G\035\\\216.\225e#\305Y\216<\325\225\217\024\375\264\252\225:\245N\211V\022:\235c\257\232\310*y\247\253`\325\330OCS\261\252\362\265@Z\215\324\340\364n\246\227\251c\220r3H\347\322\2432SL\224\236e4\311Q\264\225\013KP\264\225\013KP\264\264\303%F\322TfJ\215\244\250\332J\214\311I\346f\232_\024\364\222\236\355\306j\035\364n\245\017N\316i3\212p4\361N\003\232\221EH\242\246AS\240\251\320T\350\265a\022\254\306\265a\022\255\242\000\265:\307\306sR\242\342\255@\270<\326\244\003\201Z\021%ZD\253\010\225f5\253\n\264\375\224\365\216\254,ub8\252\312GR\004\257\230\244}\346\233\320\212\271\031\332\005M\346qU\344\223&\242-I\276\224=.\372ij\217~\rX\337\362\324\014\364\303%!\222\232d\250\232J\201\344\250^Z\201\245\250\232Oza\226\232d\367\250\332J\215\244\250\332J\214\311M2R\371\231\247#\324\305\362\265\016\356iCR\251\247\251\301\247\236FE*sR\250\346\244QR\252\361OU\315N\213S\242\324\350\265f4\253H\225:%Z\215*\312\002F\000\251\322?Z\260\221{U\250\343\253\360\'J\321\204c\025q\026\254\"\324\352\2652\212\231W&\246\t\322\254G\035YD\251\325i\341y\257\224\267R\253d\365\251\326\340\214\017J\223\317\310\250\231\362i\273\251\013R\207\243}4\2751\236\244Ir\234\324L\365\031\222\232d\246\264\225\013IP\274\225\003\311U\332J\214\311L2SL\264\303%4\311L/Q\226\246\356\247\003\232\221x\247\227\342\242\r\363S\263\203OV\251\001\315J\207<\032z\256\rN\026\244QS*\324\310\225:\307S\"U\250\343\315Z\216:\266\221q\232\231#\253QGV\322!\216\225:GV\022*\263\034uz\010\375\252\342GW#\\\212\265\032T\252\2652GV\021*UNj\332\'\0252-L\027\212r\212\371\'u\001\261N\rO\017\212\013qF\352M\336\264n\244-M/Q\263\346\204~\324\326|TfJa\222\230\322T-%B\362Uw\222\240i*&\226\243ii\276m\'\231M2S|\312\013Te\371\247\254\270\251<\352x|\212f~j\223w\024\34452\032\225\0175eFj\302\n\221V\246E\253q&j\312\307\355R\244Uj8\261\212\267\034Uv8\270\305J\260\373U\230\341\366\253+\037\250\253\021\307VV:\2364\253\221\014U\224>\225n,U\225\"\247J\262\213S\252T\250\234\325\224\025:\212\230\nP\265\361\376\374u\245-\273\221\320Q\346d\323\267r*U`x\244\'\232i4\233\251\013S\031\2522\324\253\300\3156F\342\2533\342\2432\323\032J\201\344\250ZJ\205\344\252\355%D\322TM%0\311I\346\322\371\224\236e(zij\025\351\333\261S\304\324\245\271\251\024\344S\324\342\246CV\"\003<\325\330\370\351S\252\324\312\275*dNj\364)\322\255\"U\244\2078\342\247X\261V\243N\225n5\2531\240\253J\243\002\235\220\rH\216;U\204b{T\350O\245Y\2103\032\273\032b\244\335\264\342\244B\331\310\346\255G&:\365\253qL*\332J*eqS\243\n\260\246\246S\221R(\257\215\213z\232\2267\302\034TA\260\334\324\241\263\315(~})\305\275\r7y?J\013SKTl\324\300\334\323\214\230\353PI(#\212\254\362T-%1\244\315B\362T\r%D\362Ui$\250ZZ\215\244\246\031)<\312Q%\033\351D\224\375\331\024\014\346\236\017\025,MR1\311\342\244\211\275je\301\251\220b\255F*\334~\365m{T\350\275*\334q\216*\324k\212\271\032\325\350\0235d\303\200\r*\340T\310\330\251\321\316zU\224\311\251\304\031\031<\324\251\t\354*\322[\271\253p\3321#5\261ib\253\202ji\342\000|\242\250\264M\273\245^\266Q\2140\253&\327w+Q\230Y)\213#\206\305\\\216V\342\255\307!\305[\212L\342\256\306r*\302-|`i\276a^\224\007,j`\330\024\273\263C\034\n\013`b\233\272\220\267\025\03357uG#\324\016\365]\336\241i*&\222\241y*\026\222\242g\252\362IU\332J\214\311L2Ry\224\242Jv\372pzz\275J\0374\360jD\251~\265,uaz\325\204\031\2531\n\270\203\000U\270\327\"\255\"t\253\221-\\H\363V\342LU\310\260\265)\223#\002\234\210I\346\255\305\026x\002\257Ed\314>\355\\\207On7V\214V\034c\025n\033\000:\212\273\035\242\000\t\351S\013d\316EXED\357R3G\214\001\232A\0227U\251R$\037\303R\205\031\371F\005+\303\221\3275\017\331\207a\315*\246\323\310\247\026\njX\344\346\264\255\344\r\212\320N\225\361a4\323@\342\236\0374\340\3243d\nB\334\234\323Kb\233\273\212a99\246;\342\253\273\324,\365\003\275@\317Q3\324N\374Uf\223\232a|\212\255#\325v\222\2432S\013\323|\312z\311O\022S\203\324\213%J\257R\253T\361\265MR\306j\312u\2531\212\267\020\315[J\273\n\360*\364i\234U\270\322\256D\265eH\025&\342G\025=\274e\316\024d\326\315\256\230\317\202\303\025\255\005\212G\216+A!T\034\212\231B\2020*\30279\002\246POJ\225b=\352e\2074\357\263\234\325\224\266P2y\251\004c\037(\247*T\241)|\2726b\221\220\021\315f]\023\033c\265Ij\306LV\244A\243\301\355Z\2606\345\025\361ni\271\2434\241\251CR\223J\314\n\202z\324d\344\322c\202)\214\330\252\356\365\003\265@\317P;\324\014\365\013=F\317\305U\221\351\202L\212\206V\305Tg\250\214\224\205\351\273\351\301\352Epj@\342\236\257S\253T\310j\314|\325\220*D\0305f>\265r1\305Z\214U\310\3278\253\361/\025\241\n\360*\352\'\002\246^*tN\346\255End<V\355\205\232FA\3075\265\033\0000\005J\2715aA=jdJ\260\211VcJ\260\251S\242T\253\036\343Rm\344\212\220F})\351\027s\326\237\260R\204\240\306)\214\225\233{\016\3420)\326p\354\353Z\341r\200b\255Z\251\003\006\276+\315&h\316h\315(4\273\251r\017\007\2458\000\0055\333\002\252\310\371\252\356\325\003\265@\355U\335\252\006j\205\236\242g\252\322=B$\301\250\345\220\232\246\317\311\250\313\322o\246\027\245\022T\210\365.\342*Dj\265\033U\2045f#V\321\252d\253q\n\271\030\351\212\265\030\253\360\247\002\257\302\274U\370S\201V\301\300\251\341M\307&\256,E\210\013[6V\273PdV\254H\007J\264\213VQj\312-XE\253\010\225j4\251\325*A\307\025n\025\030\247\010\306\356*`\224\316\344\nz\212~\007\343HV\223h\006\240xC\222H\2428\300l\n\323\212\037\224f\247\216,\034\327\304\006\2234\231\245\315.h&\223u(\227\002\242\222M\325\0035@\355P;Ugj\201\332\241f\250\035\252\026j\255#Ugza\223\212\255#|\306\242-H^\232^\227p\251cz\234\020E=MY\211\252\322U\250\252\352\016*t\025n!\322\257B=j\354KW\241Z\320\201j\364`\001R\250\334\303\025\243\004Y\300\025\265kl\240\002\302\264c\003\265ZAVc\025f1VPU\230\326\255F\265j5\315YU\240\307\316j\334\021\222\265<Q\374\307=\251\345qP\221\226\342\244\002\245*\252\276\264\305 \212B\200\362M5\306@\002\237\005\271$\034V\200\033p*@k\341\243L&\2234f\224\032Bi7SKTL\325\0235B\355U\235\252\273\265@\355P1\250Y\252\027j\253#Ugj\210\265A+r*\"\324\302\364\205\351C\324\310j\322\221\266\234\244\346\254\304\325r3V\342\355Wc5j1V\342\034\212\277\020\253\321\014\325\370V\257D0EX\317LU\313u\307&\266,\"\334A5\260\270\030\002\254\305V\322\254\245Z\214U\230\326\255\306*\314b\254\3060EZAO8\034U\373|2b\235\267nsQHK\034\nhV\317J\231P\234qJ\352FEV(\301\370\351V\022\022\335j\302\333\201\326\254$x\245e4\252\276\265\360\333\n\204\365\246\223FisHM&i\244\324Lj\0265\013\232\254\346\253\271\252\356\325\013\265B\306\240\221\252\244\215U\235\252=\325\024\255\305BZ\243&\232Z\205j\261\033\325\205z\231\0335j#V\3428\253\321\034\342\256F*\324uv*\320\206\257\302:U\370G\"\255\216*\335\272n\344\325\330\027{\214t\255\353e\021\240\035\352\342\032\267\035[\216\254\307W\"\253q\212\267\032\325\230\326\254\306\274\325\2001A^sWm\270\305Y\312\263`\323\035\0009\035*\274\227+\031\250N\247\351\201B^\031\\\016\265\251\034a\324\020*\302\305\216\225*\246jU\2175(\204w\250\335\0247\025\360\223\032\205\2523Gj3A4\334\323I\250\232\241cP\271\252\356j\263\232\201\315@\346\241cU\244j\252\355U\2445\036j)\017\025\016x\246\023M&\224\032\225\rYZ\2363V\343\253\221\325\350j\364F\255\307\315\\\213\265_\207\265h\302:U\370\370\305N\2373\001Zp!\332\005hZ\307\206\025\250\215V\3429\253\221U\270\315Z\216\256EWb\355V\3435n>j\302\2561S\016i\315\320T\320\266*u9<\365\245\221\266\216\265\227:\231\033\212\317\2226F\255]&\r\356\t\256\215#\n8\247\242\234\373T\333x\244I6\2674\351&\335\302\324Y\311\346\276\024\"\241j\211\250\006\202qHM!4\302i\214x\250\036\253\271\250\036\240z\256\365]\352\007<UY\rU\220\325w4\300y\346\242\220\365\250\r0\232J*D5j6\342\246F\346\256D\325z#\232\277\r]\205sWc\\U\310\205_\204c\025\241\025]V\033j\335\252\344\346\265`\255\030\260\243\212\267\031\315\\\214\325\310\232\256Fj\334f\256Dj\344f\256EWb\346\255\240\315J\027\024\377\000+&\225>Y\000\253\312\200\214\212I-\367P\266H{U;\273\020\275\005K\246\257\226\370\255\325PE;\201F\340i\n\212B*&\310\351_\016:b\2538\305@\324\212y\247\036\264\323M\3150\232\215\215D\365]\352\026\252\357U\336\253\275@\346\252Jj\244\206\253\261\246u\250\330\324,j2iz\nAOZ\231\032\255E\315^\210U\330x\255\010{V\204#\030\253\2503V\343\030\025r#\216\265r)*\354J\315\327\245_\205\266\340V\255\250\316\rh!\305Z\210\325\310\215]\214\325\250\315\\\215\252\344MW#j\271\023U\330\232\257Ds\212\2621\201Rn\003\245B\315\363\325\250\346!1K\346\2615<R\034\202j[\225\014\231\252\021\270\216Q[1\311\205\006\223y,EH\264\356\364\206\230y\257\210\235A\025RT\252\216\274\3231\203JO4\323M\2465D\306\243sP=B\365]\352\007\025]\352\254\206\252Jj\253\234\325w\246\257z\211\272\324\rL\240\232L\324\212jx\352\324uv#W\242\347\025\241\017j\320\207\265hF8\2531\324\350{\n\275\016\027\031\353Z\020\313\221\201W\355\343.sZ\220\260A\216\365r3\232\271\021\253\221\232\265\033U\270\332\256F\325n6\253q\265\\\215\372U\370[5z\'\253(sO$\366\246\204\346\246E5:%Y\215*r\273\220\212\307\234\025\227\217Z\320\266\237\200\255VP\202\306\247\024\242\202j2k\342f\025\013\256ER\225pj\022\265\023\036\271\246\253v4\036\264\323Q\260\250\232\241aP\270\250\034Uy*\244\206\252\310j\244\206\252\271\305@\335i\005B\335MB\324\312c\036h\024\365\2531\325\310\205\\\217\222*\3645\241\rhC\332\264\"\346\254\216\005K\t\3475ad\313`V\275\234D\201\232\327\215\202.\005O\023sW\242j\273\023qV\321\252tj\265\033\325\350\236\255\306\325r&\253\221\265]\205\361W\241|\325\304l\324\3523R\005\305J\242\254 \251\327\212\232.s\364\254\353\210\201r})\321\240\0035j\023V\001\247)\244\315FO5\361ST&\253\310*\253u\250$\353P\323\263\221\357HzS\032\243aP\260\250XT\022\n\247%T\220\325Y\rT\220\325g\250Z\232;\324-\336\230i\204TX\311\251\222\002\325!\210\255=\005Z\216\256\304:U\370E_\207\265h\300j\364b\245-\212zK\205\253\226\274\260&\266\241\220\200\000\253\220\271\'\232\275\021\253\261\265\\\215\360*\312IV\021\352\334MWaj\271\033U\310\232\256D\371\253qI\316*\374M\214\032\277\t\357V\343~qVP\206\251\002\355\251\026\245\335\201K\024\303\232\245qq\202\331\357L\212\1770\205\025\243\027\002\246\315*\236iI\353Q\232\370\271\226\240aP\270\315T\221H\317\245U\177Z\214\212fpi\364\204TdTL*\027\025VJ\247-S\222\252IU^\253\275B\302\233\332\2414\334Pc$Tay\253\260\201\306jFPA\305F\253V#\025r!W\241\253\320\326\2045v6\300\247;f\233\031%\200\255{a\2001Z\220\267\025v\023W\242j\267\033U\224z\263\033\325\250\332\256\304\365n\'\253\261\275[\211\352\344MV\324\364\305^\267\2238\006\264\340|qVP\374\325a%\305^\211\204\213N+\264\373S\325\014\247\013J\366\376R\234\034\361X\272\211*F*]92\0015\256\235)\333\250\r\3158\2650\265|j\353P\262\324,\265\004\211\220j\204\313\216\225\\\323\030R\253v4\374qL\"\243aU\344\252rU9j\234\225VAU\\Uw\025\t\024\323\322\242\"\205\\\323\312\361L\331\315L\203\024\346n\324%X\214U\250\352\364=\005^\203\255^\216\254\253b\236\032\226&\303\214\326\265\273qZ\020?j\277\023U\310\332\255\243\342\247G\2531\275\\\215\352\334oW#j\273\023qW\"j\275\023t\253\2617J\264\234\034\212\275o!$V\202\265L\206\254E!C\221Z(D\210\rIn\n9\367\251\'\031J\306\324\340$\006\247\331\r\250+@\032\t\244\006\234O\025\021s\234\036\265\362\003-FR\243h\352\007\217\212\316\236>MTe\301\246\025\246\355\3475&8\246\225\342\242qUd\252rUI\005T\220UY\005V\220Uw\025\013-4\255D\313\3159\006\005;nW4\230\246\223\212h952\n\263\030\2531\365\253\261U\350{U\350\315M\232x<RFI\220V\275\273`\n\275\023\362+B\027\253\221\275XG\253(\365j7\253Q\275\\\215\252\344OW\242|\325\270\236\257\302\365v\'\253\261\266j\334M\202+F3\275x5<G<\036\265>p*{{\222\204/j\322I\200 \372\324\3228#\216\365\235\250\260\362\300\250mxQW\003Rn\245\315.\352%Q\267x\355_#\262TE1Q\225\250]*\224\361g5\237\"`\363P\225\246\225\247\017J\030qU\344\252\222UI\005U\220Ui\005U\221j\263\255Wu\250\212S\031j=\2314\360\231\340R\274ei\241)\222\257\034Th\265e\026\254 \251\343\034\325\330\252\3545r:\237\265*\032\222 \004\2315\247\t\343\212\267\033t\253\3617\025n6\253(\325b6\253q5ZF\253q5^\210\346\256D\330\253q\275]\211\352\364Rr+B\'\253\221\232\275n\3705qFy\035j\300\345pjkU\035\372\325\254g\247j\236\',pz\n\316\324\244\314\241i\320\034(\251\367Q\272\200\364\355\324\273\262\214\t\342\276Rd\250\214t\302\225\004\213UeL\326t\361\363U\331)\205=)v\2201Q\222T\340\364\250%\252\222\n\255 \252\262-U\221j\263\255Wu\252\356\265\036\332\215\222\221S\034\324\210\203\322\234c\315F\321\342\242e\317Zb\245N\213S\250\002\246\214d\325\270\305\\\212\255\307V\0074\252>j\220\374\244\032\277n\331\025n3W\342<U\230\332\255+T\361\265[\215\261Vcz\271\023U\370\037\025y_ T\350\374\325\310^\257B\375*\374RV\204\022\014\001V\243\223\232\320\212L\212\264\255V\"85e[\232\231[\0035\215;\371\227\'\332\255\306x\2517PZ\223u8=9[\203_-\236j29\2460\025\021Pz\325yS#\212\243,95]\355\375*/+\035i\254\265\004\251\306j\254\202\252\270\315Wu\252\316\265Y\326\253\272UgZ\201\326\243+Q\224\311\241\223\260\247\252b\234x\250\\\346\243)H\027\025*\n\223mX\211qV\220U\250\352\334uaE?\035\351G\315\305]\267\\\001Wc\253q\232\265\031\251\321\252\324f\254#{\325\230\332\256\304\365v\026\253\250\374T\350\374\325\330Z\257D\365z\'\253\2615]\215\352\344S\205<\236*\364R\206\344\032\262\222b\254$\231\247M8X\2175\231\021\313\226>\265y\033\212v\352M\324n\2405H\255_1\025\250\312\324l\265\023\n\211\327\212\256\311Q\025\333\332\240t\311\250Y*\t\027\212\245\"\365\025U\306\r@\353U\244Z\256\353U\335j\264\213P2Te)\004tyy4\273*6\\\323<\274\363OX\367R\233zO/m=V\246AVPU\224\025f1VR\244\305 \030j\277\017J\264\206\254\304j\334f\246CV\024\342\245G\2531\275\\\211\353B\007\343\236\225ie\315X\215\363W\242z\273\023\325\370_\245^\211\352\344oR\003\271\261\232\323\200\225P\005[V\251\267\0208\252SL\354v\223SC\300\025d5.\3727Q\272\22459Z\276me\250\212\324l*&Z\211\205B\313\326\242d\250Yj\027Z\253(\252R\202\017\025M\371j\215\226\253\272\325wJ\257\"UvL\232\211\322\242\331\223Jc\300\244\tA\216\233\345d\322\230\251V,\032\231\"\315#Z\363Q\030\n\366\247F\234\325\225Z\235\026\254GV\024T\242\223\034\325\2503\212\271\0375f>*\312t\251\220\363V\024\324\250jt`*\334-WRLU\230\3375j\'\301\253\360\276j\364.\001\031\253\310\300`\203Waz\275\033\361Vm\206\351FkMxc\355J\267J\247\031\251\305\300\333\234\325r\333\3375e\016\005I\272\227u.\352]\324\241\251\352k\347\246\207\212\201\243\250\314u\013\2475\013\'\265B\353P\260\250XT\022\n\251 \353U\312d\034\326s\256\030\212iZ\211\322\240d\252\322&N*#\026\005B\351\236(\020\200=\351\217\035\'\227\212<\2726zR\371t,y52E\203S\371{\227\336\230\326\371\355Q\233r\247\212x\216\244T\251\025pjt\025 \004\324\210\207\251\2531qVc\253h8\0252\364\251\022\247S\300\251W&\247\214z\325\244|t\251\343z\271\023zU\3049\372\325\270d\305]\211\352\364RU\350^\257\305%^\265\227ksZ\261\220Ww\255S\272\217k\006\007\203R\303\222\006MXQ\212\2206)\341\251CS\203R\346\234\032\236\246\274\r\333\002\253\261\250\331\261P3TMP\262\346\240u\367\250\035N*\006\004\216\225ZD89\252\344qT%O\336\032n\312\215\226\241t\315Bb\357PH\230\246,<d\3222\034\323\032>:Q\345\361Hb\243\312\366\243\3134\242<\032\220-9x\251@\004PR\233\260f\245H\201\024\361\020\251\025\000\251UG\245J\0234\021\264\200*\314c\246j\332\216)\352jU\251\324\360*t5:5J\246\245F\253\260I\212\273\033\346\255\306E[\214\325\310\236\257C%^\211\352\3442a\205lE\'\356\361Q\334I\362\250\245\205\361V\203\346\2245?u(l\323\203S\203S\303S\201\257\004qP\271\250\035\352\273IQ4\265\023\313\305Wi*&\222\231\2734\307\\\212\252\311\212\253:\014g\275C\214\212\215\226\240n)\215\310\250\032=\306\244\021|\265\031\206\230c\244\362\275\250\362\250\021\212_&\223\312\244)\212P\225\"\255?o\024\322\234\323\324b\236)\300T\213\301\251\327\326\243\335\271\352\304g\221V\225\252E5*\232\225ML\206\246S\306jEl\325\2045b3W\242n\225r6\253\221=\\\214\325\330\217J\271\023\325\244~\365~\332\344\343i54\222n\024\350\332\254+\324\241\251\341\251\301\251\301\251\301\251\301\252Ej\360\211\rU\221\252\244\215U]\352\026z\211\236\242f\246\023\232r\212y\\\212\202H\370\252\222G\236\265\003\304T\373S\032<\212\254\361\020i\2062h\021\340R\355\342\243d\250\331(\tHc\244\362\361I\202)z\323M\"\216j`\005;\024c4\230\346\235\212x\024\341R\216\225]\270\223\332\255\305\332\255)\004{\324\253R\255J\2652T\240\372\324\253S#U\210\315[\215\252\344MW#j\273\023\325\350\232\255\306\325e_\212\232\027\303\n\272\357R#T\352\365*\265J\032\234\032\234\032\234\032\236\032\236\255\315x\\\246\251\311U$5Y\315@\306\242&\232E&)V\246Q\305+G\362\232\241\"\340\325yNF)\241\306\323\270T$\206\351HPb\232R\233\345\346\220\307M0\346\232\321\342\243+\212i\250\230\3233M\335\212i\220\n<\352p\236\244YjU9\247\250\251DD\364\247\213f\245\362\266\3655\033\305\236\2254Gh\346\246C\310\253IR\250\251TT\312)\343\212z\265J\246\254Fj\334g\245[\215\252\344mW\"j\275\023U\330\216j\322\216*x\370 \324\373\363R\306\334U\204j\231Z\244\rO\rO\rN\006\236\r<5xt\225RZ\247%Vz\201\2522(\305.\332\002\363VR?Z\216\346@\243j\325\006\250\035\t\250\231\030\016\224\301\031\317\"\237\266\232\302\232\0074\2458\246\221M`\030Ui\001\006\242\'\025\003\232\205\237\025\033IQ\027&\22015*\006c\300\253Q\333\310q\301\253q\333H\007\3355e-\237\031\332i\342&\035\2158\254\200t5ZF#9\250\222|\036j\310;\271\025*d\n\271\031\340T\340T\242\244\006\236)V\246Z\231*\324f\255\306j\334f\256D\335*\364M\322\256\302\325v6\315\\\214\003\031>\225\t\233i\251b\270\025n93V\025\252e4\340i\340\323\301\247\003R\003^+\"\3259\227\255Q\220u\252\316*\002)\204Q\212Z\232\030\363\311\245w?\303U\232&s\223J-\375E\006\334SL\000\366\250\244\200\016\325Y\223\031\250Xg\2450\214\032p4\326\3053\245G\"\203U\\b\252\310*\273)4%\273Hx\025m4\247n\306\255E\242\271?t\342\265\255tE\030$\001W\377\000\263\242U\343\255=mbP8\247\030\223\267\024\326\265N\242\201j\230\346\250]X\214\235\2039\254\271,\212\234\343\024\350\240}\330\305h-\266V\244HJ\365\253\n\274T\241x\244<P$\002\244\017R\253f\254Fj\312\036*\304mV\342j\271\023U\330[\245]\210\325\310\332\256B\340\002\017z\204\304\314\307\236*E\201\207\275X\2100\351V\243\223\326\255#T\231\247\003O\006\236\r<\032\361\347J\2472U\tV\252:\324\014)\204R\021M5n\022\014|S\322\035\334\323\314@v\246\230\352&\\TG\255\014\241\205U\232.\rS\333\202j\0318\250w\363K\234\322\212cT\016\231\250\014\005\317\002\245\217N,\303ul\331\351\n0H\255d\260\215\000\342\236#U\340\nF8\246\022MF\331\246\362)7\237Z\004\236\364\355\343\275C2#\203QG\030\317J\262\024\001\236\364\207\031\245Z\220t\250\035\300\004US!\315M\034\276\265n6\310\253Q\265ZC\232\235\rY\211\252\344mW\"j\275\023U\330\333\245Z\215\252t\346\254%K\264u\035i\244\367\035EK\024\231\253hr*AN\024\361O\006\274\226E\252\223/\025\237*\3259\026\253\260\346\243\"\232E4\2415,D\250\305_\200eO\024\346\004\236)\276Y\357P\312\270\252\376]!\\SfO\220\326pB[\030\246\334A\204\310\025\232F\032\244QN#\002\230FjH\355Z^\202\264\355\264\262{V\202YG\037Q\315M\200\275)\215%1\237\322\243$\236\264\224\322i\204\323MFN)7R\026\240>:S\274\312PjE5 <Uk\210\333\250\351U9\035i\312\325j\027\307Z\275\035\\\214\234sS+U\230\332\256Fj\344G\245]\211\272U\330\333\212\265\033U\250\332\255F\3258\366\244x\311\345z\324`\0259\253p\311\221V\223\232v1N\006\234\255^W%S\230U\031\207\025FQU\210\246m\315K\0349\353S}\234c\245:;M\307\245\\Kr\203\030\2462\200x\245+\201U\234\014\363\315D\330\003\201P2\346\227fF\rD`U\344\n\255r\000\214\326)L\261\251\025qC\220)\221\251v\300\025\277\247\331\034\002\303\025\247\263`\300\250Y\252&&\243\3154\232i4\322i\214i\231\244-M&\230i\271\244-NSR\003R\003R\006\300\247\3440\301\2523\306\003eEB\005O\0305~\002E^C\305=j\314f\256Fj\344F\257Dj\334mV\320\325\250\332\255F\325j3\232\231i$L\214\325p\333\032\256\305!8\305M\311\247\000GZp\257.~ES\226\250\313T\345\025\\\216i\361C\270\364\253\211\016;U\210\355\363\324U\330\241U\035\0056\343\n\274U\0227\034\323%<qU\230f\242aM\305(\024\307\\\212\317\272S\264\326[\020\246\2432zS\222&\224\360+Z\306\304\002\t\034\326\332\3425\300\250\244\2235]\230\323\r4\221L&\243-L-L-L-I\273\024\322\364\335\324f\2239\247-H\r<5;4\345jl\204\032\210(\31752\001Vc|U\244\222\245\r\232\263\033U\270\216j\354g\025r#W#5n3Vc5i\rZ\215\252\302\232y9\025VU\301\247\301!\007\025u\0375(j\007^+\313\244<U9MT\222\252\310*!\036Mh[\301\201\234U\225\213=\252dL\nq<{UY\316\343\201\332\242\333\305C \250\030Svf\230S\024c\212\215\306*\205\3361Y\023\250\'\345\346\231\024\005\210\310\255kx6\340\001\212\325\210\010\320b\202sQ\265FsMcP\263Tl\325\031j\214\2654\2650\265!ji4\204\321\272\2245(jpjw\231\212p|\323\303R\026\024\200\363R\253\001\324T\361\310\t\025e*\302\n\260\234U\250\232\256\304sWb\253q\232\267\031\253iVP\325\2045e\rH\r5\306G5]xz\275\031\310\340\324\240\323\324\327\226\311U$\252\262T\004f\247\267\203q\316+M\"\332\240S\266zP\303h\250\334\361\216\365Y\2074\323\300\252\322\236qQ\3434`\ncS0*\0311\315P\226#)#<T_b\000\363V\342\265D\000\342\234\355\260\361\214\323\321\313\032\224\360*\031%\n\t5J[\325\\\363U\216\242\t\347\245\037o\214\322}\241\033\241\243x=)\206ALi*3(\246\371\264\307\237m@\327\200t\250\215\343\036\224\365\272jx\270cS\244\247\034\323\213\023OV u\2517\232P\371\251\343\367\247\367\251U*\314d\216\265m=j\302U\210\352\354]\252\354Un3V\3435j3VP\325\2045f3S\nq\031\025VE(\331\02542U\260r*E\025\345\222\032\251!\252\315\315\010\233\230V\234\020\355\025c\024\207\201\357Q1\342\240s\324\324-Q\310\330\252\262\034sQ\027\003\2554\315\237jkJ=j\007\271\003\245R\271\272\003\241\346\243\206\350\036\365ed\337R\006=*\'\0074,\273?\n\251q\251\225$\003Y\263_\273\367\252\215#\267SM.GZa\227\322\201pGzQz\313\336\246[\364?z\246[\204q\301\246\271\003\221P<\307\265U\222V5\017&\246\215\t5m#\030\251V1R\252\323\302\212\177\035\351\014\253\234R\207\031\251\221\352\302\266MXJ\260\230$U\310\207j\260\023\214\212\2321\203W\"\355W\"\253\221\325\250\352\324f\254\245XJ\260\206\247S\232\220sL\221r*$\0305n3S\003^U)\252\222\034\324[rj\345\254\005\233\245i\210\266\212B\270\344\324NE@\354*\273\032\211\217<TL3\326\253L@\2522IP<\270\357U\236s\330\325y&n\325RB\317\234\322\300\010`+^.\000\251\2529\016;\325\033\213\225@@\353Y2\271s\232`J\016\005W\222AU\332J\211\244\250\332Jn\372U\235\220\360jU\275q\324\346\236/3\326\227\317S\332\236\222\'z\225$L\361VU\301\357R\006\003\275)\235W\2750\334g\356\320$8\347\232\003sS!\253\t\323\212\261\031\253Q\232\265\027QW\243\343\007\265[A\221NN\265n*\275\025ZJ\265\031\2531\232\265\031\253)S\255N\225*\322\260\310\252\303\206\305N\207\025aMyT\2035\027\226X\361SEhI\344V\224\020\204\035*f\340sU\235\262MV\223$\324,\246\242~*&5\014\207\000\346\263.%\3118\252N\365]\3335\021\353N\n\034`\323M\267>\324\242\r\274\212\235\034\255;\317\342\232\356XqY\362\332\263\234\365\250~\312GQQ\310\205\007\002\263\347v\006\252\263\032\211\230\324Li1\232pBi\336Q\243\3114\322\230\245\tN\013\216\364\360\017cR\2430\357Roj\006M<\034T\212\325:T\3501S\243m\342\254\241\315Z\214U\310\305^\217\221VS\245=z\325\230\352\344F\256Dj\334ua*\314f\254\2475a*e\251\224\324\202\241h\362\331\025 N=\351\352ppk\314\266n5f;lu\034\325\250\341\305M\267\002\241\223?\205C\345g\223\322\241p\240\361U\337\212\254\3475\013\266\001\315f\334\317\270\220\247\212\240\355P75\033\na\024\200\342\232\327\005j#r\344qP5\313\203O\216g|U\264}\243\236\264\3573\'\245)\301\034\325y\242\3348\252\022\330\263d\325\tm\031{Usl\304\364\246\233F\364\2446\305\006H\244\001\207j\031\210\246y\236\246\227p4\322@\243u=ML\206\244\342\214zR\212\221jt8\251\225\252\302|\334T\311\225<\325\350Nj\344g\326\257BF*\324~\2250J\2321\212\263\035[\214\325\310\315YCVR\254!\305XCV\026\244\007\025\"\232\\s\232\224\014\212G_J\363\310b\311\253\313\026{T\2420)\257\201P\2200Y\272U\013\233\241\321zV{\316{\032\256\323\222y4\323.{\325;\211\267p\275*\213\363P\260\250\210\250\332\231\365\250\335\2608\252\315\315>%\365\251\232\335[\266)R\035\2751N8\007\223L/\375\334\032\215\213\372\322y\244pM5\3468\342\253I.z\212\256d^\324(\311\245p\270\347\025VIQx\025Q\2432\234\203M\373&:\265\006\334\216\206\220@\304\324\211o\357R\224T\036\364\300rx\251\000\247\201NU\317J\225P\324\252\2652-L\203\025j2\017Z\267\020\002\256\304GCV\343\307j\266\225a\rL\242\254F*\312\n\267\021\253IVP\325\204\251\320\325\2045.x\245W\251\224\346\245^:S\310\334+\202\206,b\256\252`R0\252\362\270S\317J\314\274\273\317\312\275+-\344\311\252\362IPn\315#\023\212\256\365\003TMQ0\250\332\242j\256\346\241=jH\363\326\244iO\255F\323\036\334S\001$\362i\031\310\350j=\355\330\322\035\346\242e\220\372\324N\204\014\271\250|\345Rr*\031/\030gh\305T\222\351\333\275W23\032\2269\034p\rN\245\217Zx\311\247g\024\231n\302\201\0339\346\245\020m\353N\tO\tOT\"\244\037J\225@\251\227\035\252U\251S\212\265\031\253Q\232\271\023U\310\332\255Fj\312U\250\371\253\010*x\3705r3V\022\254!\253\tS\245H[\212@jTj\262\255\305H=\253\377\331"
+byte_png: "\211PNG\r\n\032\n\000\000\000\rIHDR\000\000\002\000\000\000\002\000\010\000\000\000\000\321\023\213&\000\000\010\026IDATx^\355\335\333\266\243(\020\000\320^\231\377\377\344\311\232\311\255\317\211A\243\010J\301\336O\335&\022\254*\020r\351\376\363\207q]\247\007\000\000\000\350\323ez\000\000`\216w\216\001\000\000\000\000\240y\276\007\320;\037\330\000\000\000\014\3046\037\000\000\000\002\261\221\357\231\354\002\000\000\000\000\000\000\020\224\237\247\237O\016\000\2003\370\352#\000C\370\262\351\256}?\254\335>\000\000\000la\237\272\326\2277\024\2003\031\240T\241\260\000\200\026\014\277&\271\016\037\001\250\301\300\002\000\200q\244\277\022p\271\244\217\263\235\035\026\000\000\020\200\255K\232\270\220\2440\350\225\332\006\000\000\000\000\000\000\200\306\370y\017\000\300D\344\005\222oj\026\220]\000\201\242\237}\215\335)\023\211@\251\007\240Weni\014E\321@P%\266\037\327\177\247G\3063\356$x\235)\242q#RV2\270-\222pT\001\037\224\004\014\305\220\007\310Sl\323\227?\021\347\237\311\016\037a/V\nG\t\327a\000\240\214\217e\014\000\000\0004\315Nv\007\237\005@\017\214d\000 \213\275\024@\030\246l8\235a8\250\265\211_\373<\000\000\200\265|\017\000*1\270\000 &o\304\303VF\r\0000 K \000*q\2139\316\330\261\366I\346\237\177\246\007\016u\271\304*@\005\223ic\340bU\305(nY\t\233\231\260\035o\207\020\016@\222a\025Ce\233\237E\340\306\345`\253\024\300\031\352\025\317\346\226\317.\200g\2077\367\033\316r/V\025\013\035+6\300\2135\304\261v\254\215\2469\23745}\030\350\336\216\t\205\346\310\346\300\336\223\177\335vCO=;u\254\022\205;c)0\357\371\271?\363\357\323\227\316\333\250`S\354\224\316E\372\350 \016\234\2456(\220\222\307{z\253.\257\300\253\235*z\377w\233\317\362\374#w\303G\256o\337\322\373\355\361\275\276T\037a\234\236\311\332\245\332\261\323s\027Q\007AK\r\231\313\333\205\335\277\352\237zZ@M\\F\023\235xIv\246\334\257;\236\205\264k\240\354:y<\305r\307\010\272\036]?\027WnJ\353\315\257\002\350\272\026\000xi\366\226X\362>\324\354E\236huLJ&\342L\253/x \367\334\326\nLf\273\275\324\333\350.\271\005\020\226\312\205\035F\2330\000\206\222\275Jrw\250Fh\271d\017\3142\376\326\340\311\375\240\0029\005\240\tV\274p\234\357\343\355\3733b\n\264\364m\246\253\315t\204|\257\001\375\1772o?/~\3765\177\230\'\213\"\2779\340T\311\021\r\000\014\315\372`@_\222\036{\303\367\345\342z\365\223\264\330\351\353\234\344\214\315\017\3449\314\255\326\006\275\035\002\360#\275\364x\334 \334&\016\220N@}9\257\233s\016\007\311\034\255\263\247\315>\300(\214\367C\031q\000\000\235K\254\257\217Z\003\336^:\361\362ws\307\227\034\325\357~\345D\035\000\212r;?E;ao\247\'\024$\255\300K\353\363\201MqE\333\223/\035\345Mc\272=+@H\323\301\177\214\274)&\357,\032\2646\225k\237G\001\347\314\005\234\312\010\033\314$\341\341\006\275\202\355LN\005*\202\200$\215\202r\346\r\326\020Y\240M\226\021T\244\274\356\002\205\241\314r\245L+\r+\221\321\022m\324\327}*\363\304H\036\000\000\300\273\373^\346\366\357V\333\353m\323\326&pO\366\236W\262\247\t\222\202\204\364\270R\016\022\020\000\030\213\033t1\307\255\252\226Ii\023Z)\207\251\352\375R\177\215\223\240\261]\025\300\030\344\231e\325\027\003\023\275Wd\357\327GS\224\333fG\317xU\225\310\177W\001\001\322\322\003\3752\367\000\235\221f\036T\302\340v\177\003\354\262\343\\\032\223\223\312\234s\310\'\336T\245\300:\363\270\307_[^\353\265\334\267\320\226\003[r\250\337~I\002\214hy\236!\214\334D\346\236G\'\024\000@\024\211\031\273\3517\t\250\344\261s\177O\275B\030\234\002(\372\366\0304\250B\211Wh\222\352L\367\274k\254\"L+\301\224I\330O+e\332[\322X\305\237\353\025\214\374\240\024M\235w&\026\255\213N\201<\254t\334+\361\260\256\002\310\"\270023\000c\353\177Ig\214\337\t\303\340f\n\340\232\271\007\337=q\354n`^\305\246\343\232I\263X\3018f\246\001F\241\000\000\370\253\231]@3\035\031\324\001?\370\336\370\n\326+\017\033\303\026@\177W\004\001\031\210\320\260R\003t\266\235\331\007\0060\304\002s\335E\256{\026\364c\300\251\3170\347I)\3600\340<\310/\362\017\034\312\244\003P\313\3103\254\315\355J\267\"y\025J\253A\363\177R\325\326j\346\001\000\0006zno\326\356r\326>\017\000\200#X\235\215\300\'>\207z\206\273\335\261\365\3363\325Q2S%\333\002 $\267\002\000\240+\3365H\213\037\227\330\313\326\370\361\247/\261\307\023@\014\346Z\232\024bY\372\326\311\020=\216\342o0MP+\tT\033^\225k:\330+^\004\357\377\213E\274n\003\005\271\027\227 \212P\2261\005{X\3363u\275\225E\370\302\010\177\001\'\022;\240W\366\r#X\270\213)\000\200w\013S\346>U\032N\314\342\211C\213\266>\277\204\353\031/\332\233IA\025\ti\221FV\2512\034\006\261\030\273\305\007\233\361\253\320bt\270\005\223\321\371\036\270\2310~\214\350\327\201\217\007hV\361\\\335\277\330\004\204\264yB\230\216\367{\003\323\2034\346\232\221\351:\224\312I\236\201\377)\203\234L\334\316y\235\367j)\247\035\372qidf\341`\006>\360\303\2140\n\367\374\215\014\r\030Q##\377\327\367\320\357\223\267\217oZ\223*\224\337\331I=\276\305[\332\211D\326F\267\267\002\366N\036@4\257Qo\364\277\023\217\256\364\234\316\2757\376hF\273\336\257\366\006\344q\376\3077\345\247\177/-{L\326\356\330\313\257\016\036\365\222\247h\372\342\026\252\244\351~\227\262p\375\364m\210\372\246\032\365\003\301\275\177&\367\366\207\rr\316iC\334\236\307b\235\231\253v\205^sr\263\241S\031\2553\006\245\001\000\000p\246*\273\262*\215\022\301\206\367\212\032sV\321\306\215X\302\245\257\313\201\263\2345\037m\027\247\247\241\010+\320,\377\034khU\263W\265\361z\202v\273\025\327\217\355\354\256o\232U\2605\301[\237\017\254\031\354FV\347$x\237\250\361{\374\3703j\357\331l\315l\017\324\267b\3325\\w[\021\345\272\016\313\341\351W\312@\016+kj*\232F3\320\340\024\300\331d`\352m\206\353?<>\351\345\215z\340TE\227\230\305\233\343\253\3523\310\363\277\240\370\370\247(\327\252\336A\362\345&\265\260 \353\242\030\275<Y#%\005\361\230a\000\330\343\324E\330\334V\261\263\233\333\221\2273FDYo\246\"\200.|\033\341\337\036\007\000j\330\260\375r\263\036\333\206R)h\356\235\030 \251\346@\255\3316p\276\364\030O\037=\312\271\257>\246I\314\367\245 \347\354\337\337\251\3139\177L3\221Z\273\216^\216y\342\320\321\326^\010o.{\343\266\367\374%\305\276=[\252\235\n\372\330\311\026\313\324/\345\002\363\210q\271\366\272\262=sa\003\031\266\343Um/\200C|&\253\321\216\306St\t\377\'\225\252]\n7G4\351\002\330]\246p\223./\226\030|\203Z\231\370Rc*\361r\245\232^\345\320\027\013\344\371c\371\300\266\365\277\306\033\032\215\032\347J\001\236\252M|\026\021P\201\201\005g:{\004~\274\376\307\201\031\033o\366\033\237\036\337\177\034\3615X\n\320m\016\000\000\000\000IEND\256B`\202"
diff --git a/core/res/geoid_height_map_assets/tile-5.textpb b/core/res/geoid_height_map_assets/tile-5.textpb
new file mode 100644
index 000000000000..0cb548900d58
--- /dev/null
+++ b/core/res/geoid_height_map_assets/tile-5.textpb
@@ -0,0 +1,3 @@
+tile_key: "5"
+byte_jpeg: "\377\330\377\340\000\020JFIF\000\001\002\000\000\001\000\001\000\000\377\333\000C\000\004\003\003\004\003\003\004\004\003\004\005\004\004\005\006\n\007\006\006\006\006\r\t\n\010\n\017\r\020\020\017\r\017\016\021\023\030\024\021\022\027\022\016\017\025\034\025\027\031\031\033\033\033\020\024\035\037\035\032\037\030\032\033\032\377\300\000\013\010\002\000\002\000\001\001\021\000\377\304\000\037\000\000\001\005\001\001\001\001\001\001\000\000\000\000\000\000\000\000\001\002\003\004\005\006\007\010\t\n\013\377\304\000\265\020\000\002\001\003\003\002\004\003\005\005\004\004\000\000\001}\001\002\003\000\004\021\005\022!1A\006\023Qa\007\"q\0242\201\221\241\010#B\261\301\025R\321\360$3br\202\t\n\026\027\030\031\032%&\'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz\203\204\205\206\207\210\211\212\222\223\224\225\226\227\230\231\232\242\243\244\245\246\247\250\251\252\262\263\264\265\266\267\270\271\272\302\303\304\305\306\307\310\311\312\322\323\324\325\326\327\330\331\332\341\342\343\344\345\346\347\350\351\352\361\362\363\364\365\366\367\370\371\372\377\332\000\010\001\001\000\000?\000\325\210r*\326\332c\257\245S\272\203zq\326\271\353\244*Nj\203>*&;\272S\243OZq\217\212\253\"Ug\025\021\025\033.*\"\265\023\212\254\313M\021\344\323\230m\340TMQ\363HN\005D\304\323w\221NI\260y\251~\320\244t\250\245UpNj\250\201[5\024\226\313\216*\224\220`\324~MH\251\212\260\210jeLv\247\010\363\332\236\"\245(\007|R\205\007\2758*\216\324\274P>\224\360=\251\340T\210\2652\n\235\026\247E\253q\255Z\215j\344kW\"J\267\032U\224\\U\204\025:-[\215j\312-Y\215*p\000\247n\354)\342\244\002\245U\251A\342\245^+\224H\360j\310N)\205EB\351\3075\215\250\332\203\222+\233\271\214\2515\004g\346\346\256*\361JEV\225j\233\255DF*6\025\003T.*&Zh\030\2465D\334\232i\024\302)\214\265\023-3i\245\333A\004\212@\215\332\227\313\343\232i\266R3P\274\000S\004C5:E\236\3258\207\003\232]\241z\322\023L\306\343NU\003\245<&z\322\371b\236\020R\355\245\013R\252\324\252\265:\n\260\213V\243Z\271\022U\310\222\256\304\265i\005N\202\254\"\325\230\326\256D\265e\026\254\242`PF)V\244Z\221y\251\207\025\"\325\210\327&\271\245^j\302\307\305F\361\324.\244\203\212\315\271\214\220w\n\347\357m\216N\005d\264e\036\255\306r\005+\n\255\'5]\222\240u\250\030TL*&Z\211\205FE7mFS\232F\\TdSJ\323\nR\010\251|\272M\270\355Ml\343\212\210\356\244\332\306\224D\307\255H\260\016\365(@:\n\010\250H$\346\223\031\245\013O\tO\000S\250>\324S\224T\2503S\252\324\350\265b5\253q-\\\211j\344b\255\304*\312\212\235\005Y\215j\324kW#\025e\026\254\252\361\315F\364-J\24352\214\np\346\254D\274f\254E\367\253\237)\212\221\007\024\025\315@\311\203Un\242\014\206\262\036\000\344\202+\026\356\334+\236*\262\246\332q\034T\016\265\023/\025]\326\253\270\250XS\n\324L\265\031ZB\224\302\224\306J\217m!ZM\224\340\224\326\300\250Z\233\326\215\236\224\345^\330\251\004g\256)\3338\351M)L\332O\322\232\313\212hQ\353K\201J\0058\n\\R\201O\013J\026\246E\253\010\265:-X\215j\324kV\343\025n!W\"Z\262\213V\021j\314kVcZ\266\202\254\247\025)l-E\324\323\305M\030\251\324qNQS\257\240\251\342Z\306)\236\324\340\230\024\205j\027L\325k\205\371Mf\204\371\3533P\207\014H\025\224\313\212cTn8\250\034Ug\025\003\255FR\230R\243d\250\212\321\266\223m1\222\230c\246\371t\322\230\246\232\211\206j\"(\013R*\342\237\200)~\224\264b\243lTE3\336\223\313\367\247\005\024\360=\005;fz\321\263\024\273i\333qJ\00752-XAV\021j\302-Y\214U\250\305\\\211j\354kVQju\025b1V\243\025j5\342\247QO\3054\256\r9EJ\234T\353\315H\242\254 \253\n0++m.\332aZ\211\205W\225A\0305O\311\352k#P\307#\275bH\274\232\211\2050\256j\'Z\205\324T\014\242\233\260\036\264\326\213\025\023GP4t\315\264\025\244+L+M\"\243e\250\231i\205i\205i1\212N\224\233\21581\247\002i\371\310\246\220\r0\250\244\331\232p@)\352\276\325 L\212]\224\334{Rn\\\342\236\005L\202\254\242\324\350*\302\n\263\032\325\270\326\256\302\265u\027\212\235\026\247E\253\010*\312\n\262\203\212\231jA\3158\256G4\320*U\251V\247\214U\250\327\035idp\200\346\251\021M\"\241\225\266\203T^\340\346\2432\231\010\024L\010\210\342\260n\306\342k6H\352\006\216\243d\305@\365]\306j\026\024\312z\034\360i\255\035@\321\324L\224\320\264\205i\245i\205j6J\211\226\241aL\"\223\024m\244+F\332p\247\204\310\250\330\020x\244\301\247\001OT\024\360\230\245\342\223u79\2441\344{\323\343\007\241\253(*\304b\254\"\325\210\326\255\306\265n5\253\221-\\AV\021jtZ\235\026\254*\324\353R\255J\264\343\300\246\255J\005L\213\221V\243\000\n{\316\261\257^k*\352\3739\301\253\246\230\307\025\237r\3475E\315\020\374\315\305Y\237\0011X\2271\362k>D\250\031*\274\242\251\3109\250H\246m\3155\242\250\310 \324\313\363\255F\361\324\r\035G\267\024\025\2462\324dTl\265\013\n\211\226\230V\223e.\312M\224\205iU1Noja\244\300\245\340R\206\024\027\250\313\023N^jUZ~\336iv\340\203S\3063V\221j\302-Y\215j\324kW#Z\271\032\342\255\"\325\224Z\235\005N\202\245\025*\232\221j\314c\212k\036iTT\261\256ML\\ \252\322\337\204\350j\224\267\245\272\232\253\270\312}\253\245<TN+:\340u\252R\016\rE\014\233d\253\023J\010\254\371\271\252R/5ZU\252r\257\006\2522\234\324e)\230\307Zi\344Te\t\247\306\2705#G\232\205\343\250\032:\214\246*2\264\302\265\033-D\313Q\025\246\225\246\355\242\220\212JF8\351Q\223\2323HO\245!\367\244\335\216\224\231&\224\n\225EL\2434\374`S\212\344\212\261\032\020\325m\026\247AV#Z\267\022\325\270\305\\\214U\230\305X^\005J\225:T\303\255H\2652\216jq\302\324}MJ\253S)\300\250g\014\340\366\025\223pB\344f\252\002]\272\325\350\227\000WHG\025\024\230\000\326e\311\347\212\244\307\255V\301\335H\354sP\2775\013-W\2213T\344CU\335@\250\231sQ2R\010\271\247\210}i\255\026\332P)\032<\212\201\243\250Y*\026LTl*&\025\013\naZc-0\256)\244SH\244\305!Zn\332n(\013\232k!\246\343\024\n\225EH\005<S\327\222*\310\2178#\265N\203\326\254F\265a\026\254\242\325\230\305Z\217\265Z\216\254\245N\265:\n\262\202\237\212\221j\302\017ZWn\302\221j`jU\246J\245\201\305a\336.\3065\005\270\346\257)\342\272\031$\n*\224\323\0228\2522\261&\252\266i\270\3438\250[$\323J\323\031*\007J\257$UY\340\315@\320\221M\020\372\323\274\254R\371u\033\246j\002\204\032Q\356)\0320j\254\221\324\014\276\265\013-D\313Q\025\250\330S\010\246\021L+M+I\262\215\264\302(\t\232x\216\202\242\243(\r7e=V\245\013\305\033i\350\2705r>EL\253V#OJ\267\032T\352\270\251\343\025j5\253(\270\253\010*\302\n\262\203\212\235E>\236\202\246\007\002\230[\232z\232\2206)\301\352@r+\013Q\220\0075\005\261\310\315\\\007\212\336\237\322\2522\325y\022\230\2109\334)%A\216*\261\213\322\233\345S\035p*\253\014\232c&j\026J\211\343\250\374\272B\224\335\264\326Z\201\326\230\026\215\265\014\261\367\252\256\225\013%B\311Q\262\324,\264\302\264\302\264\302\264\233h\333F\312iNiBb\224\214t\250\230\032m.)B\324\200R\201O\013\212\2263\203W\023\006\254 \003\025e\0179\253\t\311\253Q\245YE\253\n*t\031\353VQj\302\216*@qN\0075\"\323\213qH\274\365\251\001\002\223\314\317JUl\232\260[j\023\355\\\276\2416\351\210\007\275X\265\030QVI\256\226X\362*\233\251\007\245!\213\"\242h\366\324.3Q\354\246\262\325y\026\2520\301\246\221Q2\323\n\361L\333M+M)Q\262\324.\225\036\314Q\266\221\223\"\251\310\2305\003\255B\313P\262\346\242e\246\025\310\250\312`\322\024\246\354\240%.\332M\200R\025\244+M+\232aJM\264\340\264\354R\201O\307\024\016\r[\205\262*\302\236j\324F\256\306*\334hx\315[\215*\302GV\021*T\03056q@\346\245Z~\354R\016z\322\226\240\232E5*rE\027\223yp\266=+\224i\014\267\034\372\326\274<(\251\031\253\257a\232\205\343\250\031j\027\031\250\nS\n\324l*\254\203\255Ve\346\230V\243e\246\021L#\024\230\244+M)Q\262Te)\214\265\031\340\3242\307\273\232\252\311\216\265\013GP\230\352&J\214\2450\255&\312B\224\233x\246\355\305&\332]\224\326\025\031\\\323H\244\305(\024\270\247*\324\230\243fj\304K\267\255Y\215sV\342N\225v1W\"<\n\270\235\252\302\324\302\235N\025\"\324\231\240ry\247\026\002\214\323I\315*\361R\306~a\212\253\253I\266\023\315s\226\243t\244\326\322\034\n\031\253\271+\201P\275Ts\203Q\036i\254\264\302\271\025]\324\203P:\346\2532b\242e\305F\302\243\333C-G\266\223h&\224\255FV\230\313Q2\324\0169\246{\032\202T\364\252\344Tl\242\243d\250\231*\026Z6\323J\321\266\232V\233\212R8\246l\3157m\006<\212h\212\235\345P\"\346\236#\245\331\212U\0375H\300\361\212\265\000\343\232\271\030\253iVc\253q\0360j\322\232\225i\364\340i\341\251\331\244-\212\001\247\nu(\251c\342\262\365w\314x\254\333(\361\315h\203\201Lf\257Aq\305Wq\305A\345n&\230\320\343\221Q\225\246\025\250\035*\006^j\007LT\014\242\242d\315&\312aZ\215\226\233\266\220\212a\024\3223Q8\305@W&\230\313Le\014*\007\212\240d\305D\313Q:\324%h\333HV\200\264\2333L1\322l\243e\'\227HR\224%8&i\342:_.\232\313I\262\236\007\255X\217\002\254F\325j3\232\267\035Z\217\203VS\232\235)\304\320*A\357J[\322\223\2558\nz\323\361\305\000T\313\302\223X\232\223y\222\005\024\220\307\261EHMB\355^\222\303 \324\016\264\213\036\325\317sQ\225\354j\273\256\323Q\232\211\305Us\203P\310\300\212\254\335i\204Rb\230\304S\010\244\333M S\010\024\302\265\033\246EWd\305F\302\243#m5\230b\242!Z\242x\275*\273\307\212\204\2574\233h\333M\305.)\n\342\232\027=\251\3333I\262\223\313\346\202\224\233qO\002\237\266\230R\220\2554\014\032\231*e\340\325\310NqWc\251\322\255F*\302\234\n\\\346\234\242\237\264\321\212p\024\340)\302\236:S\221w\032m\324\302\030\311?\205c\'\357d.js\300\250\231\252\006j\364\362)\247\2574\322sQ8\035\252\274\303\"\253g\326\230\334\325i\227\212\250T\346\230V\230V\232EF\313I\212c\036\324\303HE4\212i\034T,\274\324\016\274\324L\271\025\023-DS\024\231\301\250\244\346\253\224\346\223m!Zi\024\230\247\000\017ZpP)\017\024\302=)\240\034\323\266f\217.\224/\265=V\227m1\226\232#\334x\253P\301\201\310\251\032\014\364\247F\214\235E[\211\263Z\021/\002\254(\305<t\247\n\225EH)qF)@\247\201O\305=\230D\204\232\302\274\2717\022\355\007\212\2225\n\242\2075]\315B\306\275T\216*2)\206\243j\211\207\025M\324\202qQ1\250\233\232\201\222\243e\250\230TD\021M\357JG\025\004\213H\007\255!\244#\212\211\2526\250\335r3P\225\305D\302\242a\305D\303\025\023S\010\244\305\005j2\271\246\225\244\305\030\315\000{\323\200\035\350\300\317\025\"\256i\305@\246\021@\024\255\300\250\031\262j\325\274|d\325\221\307J\221\005X\010\033\265H\220\000sV\221p*e\247\201N\002\244SR\212Z\\S\200\251\000\247\242\3675\233\251\334\355R\252k2\3352w\032\267\234\n\215\332\253\273T,\325\353#\2450\212i\024\302\264\307N*\253\2575ZT\364\250\010\2460\250\234T,*&\025\t\353G4\204f\233\214Rb\2028\250\312\372\324r(=*,qP\270\250\030TL*\031\005E\212M\264\233qA\034SqMaM\305\0053\322\233\267i\245#\212j\360j`h\316iqN\013Mu\250\322\034\265]Q\265p)\300T\350*d\030\251\324\324\252ju5 \247\001N\3058\034S\201\247\2575*\255H\005\023H\"\214\375+\234\271\223\316\227\216\225$ch\247\026\250]\252\006j\205\215z\332\236)M6\232N)\255\315W\221j\254\225Y\226\243aP\275Dx\353Q\265@\303\006\233\232v)\204sJ\005\014\274S\nqQ0\305D\303\212\256\342\241aL)P\262sQ\262S6Rm\244\"\233\212B\264\335\224m\244+\232n)\n\322\212u:\236\2640\317JX\306*QR*\324\350\206\254(\002\244\003=*E\025*\324\313R\001K\216)1\353R \251\2213S\204\307Z\216i\204JI\254K\313\343&@<UhT\236OZ\261\322\243f\250\035\252\026j\211\232\275q\rJ\005.\332\215\226\231\266\241\220u\252R\214\032\200\232\211\352\023P\311Q\324L)\201y\2511\3054\255 \247\036E4\257\034T2-@\313P\262\324,\264\312C\036\352\211\243\250\312\323\n\324dSh\244\315\035i\n\321\262\224GA\217\035\250\331F\332P(\247(\251TT\3121Rg\0254{Oz\235F:sR\nz\232\225\016jQ\322\2274\001\223S \253\000\205\0243\340Vm\365\332\200T\236k\025A\221\363\3335mF\000\241\215B\306\240cQ1\250\311\257\\\214\325\210\316MH\303\002\243\316i\255PH*\234\313UY9\250\331j\006\025\003\365\250\311\301\244\"\223\024\206\222\216\264\230\245\003\212\212E\250\035j\026\025\023\n\211\222\223\030\240`\365\2441\203Q4 \364\250\0319\3057\312\246\262\342\233\2126\322\355\247\010\351\341qHE7m&\332M\264\005\247\205\247\201R\n\220sO\013\351R)+R+\023S)\251R\246\335\201M\315H\206\247CN.;\232\257qp\241\t\006\271\371\244i\245\340\325\230c\332*S\305F\306\240sP\261\250\231\2522\325\353\360\363\305]\2120\0074\222\361\322\242Z\030qP8\252\322\214\325r\265\033-@\351\212\256\351U\331i\240Rb\233E(\024m4\355\230\025\033\255B\313\232\211\222\242d\250\312S\nSq\212i\024\303P\260\346\220-!\2174\323\035\001)\302:v\312B\270\246\342\215\264m\244+@ZP\264\340)\300T\212*QO\247\255H\225:\320O4\242\246Q\305H[\002\253\317\'\312H5\213$\256\354FN*h#\000s\326\254\343\003\212\215\315@\355U\335\352\027\222\241/\232aj\366H\276Y1ZJ\006*)y\250\361KP\270\252\356\271\250\031j2*\'L\324\016\225\003G\232\214\307Ld\244X\211\243\312\346\234#\247yt\245*\'N\265\\\2550\250\2462T,\225\031ZaJiJc%D\311H\026\224\241\246\355\366\245\tK\262\223\030\2460\3153m\006\234\007\024\005\311\245+F(\305(\024\361N\025 \247\255J\275jaJ94\354\201N\rMy1\221U\244\311\3435\003\332\356\031\217\357TA\214M\265\3705:\311\221H\304T.F9\250X\002\274TE\021\201\301\250\377\000v\271\365\246*&I\'5\354\261\304K\364\253d\020\265\016\t4\270\244\"\243~\225]\373\324,)\204sLe\250\035j\"\224yY\246\230E\'\226\000\243\313\024\276]\036]F\313\351Q2u\315@\311L\331M)Q2TL\224\302\224\323\0354\2450\305\232o\225K\345\323Lt\335\224\036\005BFi1\212i\031\246\260\245\035)\300`PM&(\240u\247S\305H\265\"\324\240T\203\245&H4\240\344\323\267b\240\335\311\240\234\322+\355<Uk\245\017\363\n\254\222\02585`>i\216\001\006\240u5\001R3Q\0255\031\316+\337\002*\364\246Hs\322\242\034Rri1Q\275Uzn3Me\246\021\232\214\246i\246*\004t\326J\217fivb\233\232v3\322\243(ED\310MD\361b\230c\366\2464u\023%FR\231\345\321\345\346\232c\246\024\305\n\240\236i\216\203\265DEFE0\256i\245qM+\353Q\2650\323\227\2458\n1HE6\212p\247\212\221jE\251\226\226\202h\034\323&|\016*\020\334R\202i\245\251\205\263PN\230]\303\2556)2*\\\344TmP\265B\302\230\302\275\325\230\223M\'\327\2553u&i\t\357P\271\315Wzh\353JFi\204S\225)JRy^\324\306\212\231\345b\230c4\301nX\346\245\021\000\005\006,\324f\034v\250Z,\236\225\033G\212\211\222\242x\352\023\035/\226)\245)\214\270\250\034c\245F\006)\255Q\225\3154\256)\207\330S\010\250\332\242aM\"\225y\247\n\\R\021Q\232QJ:\323\305J\274\324\202\236\016)wQ\234\323\272\n\212R\270\371\215D\030R\027\250\313f\231\234\032\224\'\232\244\032\240\352`\223\025:\234\214\3225D\303\232\211\252&\036\225\356\254\270\250\3150\232@i\030\346\2425\023\nh\034\323\261I\212\231Tb\236\020\032R\243\025\013\255Bh\t\232]\270\024\320\271\346\235\266\215\240\324m\030\025\033C\270dUW\214\203\322\242)\236\325\031\216\233\266\243a\315E%@\313Q\225\246\354\246\025\364\2462\201Q\265D\334\324dS\010\246\021M\007\006\244\352(\315\004\323\0174\200S\200\245\251\024\342\245\rN\315&\352p4\342\340\n\241p\344\26501\247\346\223v)\240\345\252h\337k\324\027\250X\356\250\341l\255Hj&\250\230TF\275\325\352\006\250\330\322f\2239\246\236\365\031\244\305;\024b\236)\340\322\265F\336\365\036\334\322\343\003\212M\271\247\005\243m\001)\031j\"\010\250]7v\2506\020\324\311#\347\212\205\223h5Y\205FV\243+M)L`1\357P\267\035*\027\250\2150\212i\\Tl2j2\264\303\301\245V\305;9\351Hi(\006\2274\264\345\247\212v\352\\\322\346\243w\305T\221\262\324\253N\3155\215,JpX\323\315%\313~\344\343\232\243l\374\220j\331\351Q\265D\325\021\025\356%\270\250\332\2424\224\235(\3051\205 \024\360(\333K\266\234\0058\014\324n\231\245\013\305\0333K\263\002\215\264m\315;g\024\306\002\242+Q\262\324,*6\030\252\262\014\324\016\270\250\312S\010\002\243`*\027\305@\325\013\323\n\036\246\223\201Q\261\346\2439\246\032\214\212f9\247)\245\315!\244\024\242\224\361NZ\226\212L\320\0335\023\344\344\325|d\323\205:\232\3252\377\000\253\000Si]wDEe\257\311.*\3709ZcTmQ5{fsHi\270\246\221\212JP)\n\322m\247\001N\002\214S\202\323\266\342\223ni\3018\244\333\212B(\3058-\0140*\007\025\031\366\250\230\361Q1\250\217\275B\342\253\260\346\243aQ7\025\003\232\205\215Dj&\034\324m\222i\245x\246b\230E1\205FE4\255&1A\244\315\002\234\264\032U\342\245\007\212\t\246\232\024\363N\342\253\310\230<S@\247\001H\302\244\204\344m4\2450i[\345^k\036f\377\000H8\365\253\310~QCTL*&\025\355C\255:\214SXS@\247\205\245\331K\262\223m.\3326\323\302\320iB\323\261HE4\255\001i\341x\246\270\252\356*\006\030\250\232\242aQ\270\342\240l\324D\376\025\023\232\201\315@\365\023\n\214\217Jcq\365\250\210\3151\251\270\342\232\325\013\032a4\334\321M<Sz\236(\247\n\\\361H\r<\032\\\321J:\322\323Xn\250\312\021H)q\3054\214\034\212x\224\367\243%\301\315d\314\000\270\343\326\264#\037(\241\205D\325\033W\264\201N\305(\024\025\315\001)\352\224\375\264\273i6sMd\247\204\244aH\251K\214R\342\220\255&(\003\232\220\014\212\215\305WaQ0\315W\224\343\201U\036LTfBi\205\261\326\243b\010\340\324\'\223P\311\317J\200\323\010\3151\205D\302\230ED\302\232x\250\336\241j\214\322\037j94\323B\014\320\303\024\320i\335\251)\300\323\201\247\nu)\034Rc\024\244pj\035\264\264\021M#\024\341\367X\326C\374\323\3765\245\030\371E\rQ5D\302\275\245\016E<\014\322\201N\0034\360\264\365\030\024\354f\200\264b\215\242\215\264\2052i\312\264\215\035.\312M\224\233(\330\007Zq\034qP\270\250\030T\022p*\204\362c\201T]\2114)\315+.F* \204\032G\025]\315@y8\244n\005Bi\246\243aQ\265D\306\241sP\261\250\311\246\203\223Rf\232}\350\r\203C\220E0S\373Sh\025\"\212x\247\001N\002\227\031\243\030\250X\037JA\326\235\212i\024\262|\2201\365\254t\033\246\255$\351CTL*&\257dS\201S!\315J\0059E<\nv3N\002\227\024\241h\333H\306\221Fj@\264\355\264yt\205qH\026\215\264\204Uw\025\021\\u\252s\347\234Vl\334\036j\253\002\335(D \324\342>3Lu\364\252\262\034\032\204\340\217z\201\206\323Mc\221P\2650\232c\034T\016\325\0135B\315Q1\250\3157q\355N\317\255\004\323sN&\201KE\003\255J)\302\244\002\224S\300\245\307\265\0057\014Ur\273X\212\\R\005\311\250\357\316\310p+2\325r\371\255 8\246\260\250\232\241j\366E\351O^\rZ^E<\n~)@\247\001N\240\361I\270\na\303\037JT\0252\212\220\npZ\ng\265FS\024\230\2467\025\013\016\365\003\325g@A&\251=\276\366\366\250\336\334/j\213\313\013A \n\205\330b\263\356\034\202qU\274\323Am\335j6\250X\323\013TL\365\0035D\306\242cQ1\246\026\246\347\232]\324\205\361M\337OBZ\244\351M\007\232x\024\240sR\250\315;\030\247\212p\024\360)\330\245\002\253\310\270z1J\253\315S\324\217\312\005U\264\\\n\275\216)\214*&\025\013\n\366E\034S\300\253\021\2361S\250\251\002\322\355\245\305\007\000f\243-H9\316i1R\240\251\007\024\340j\302-+\n\214\212\215\252&\300\250\034\325g\3115\003\324y\002\241\221\252\254\215P\263T\016\325J~k>F\330jDp@\2476*\007aP\267\265Ws\212\205\232\242g\250\232J\210\276i\205\251\241\251\333\2513M&\237\033T\244\346\205\034\325\2208\243mH\243\212v)TT\200S\300\245\305(\246H\2319\246\355\247\204\302\223X\367\255\276B\007J\222\336<(\253\030\250\336\241j\205\253\331\224qN^\265\"\037\232\256 \310\251\000\245\305(\025\034\265\0363O\2152y\247\024\000\346\234\007\034R\323\324U\244\340R\265B\325\003\236i\2147\003UO\007\223Q;dUv\340UWl\032\205\3375Y\316MD\306\240sUf=k.v\353Kn\341\207\275H\357\212\201\244\006\242g\250Y\263Le\315@\353\212\254\324\302p*2\324\335\324\340\324\271\246\026\247\304jqR\240\346\247\307Jx\024\360\264\355\271\247\205\245\003\024\361N\305.)6\344s@L\221Q]\270\2110:\326:\251\222L\232\272\213\201Jj&\250Z\241j\366\304L\212f9\251PU\250\316\005;94\345\346\236\005G\'&\221W\234T\244`qM\344\322\343\002\224\n\224\n\231\017\024\023Q9\250\017\255\034\021UYpOz\202CU\344\351Tf<\361P1\250\034\324D\343\255F\304\032\2550\3105\225t\207\223T\321\312\036*W\230\260\346\241\337\223C\270\307Z\256\317\317\024\3174\212C.j\0269\246\021\232\211\226\242<P\032\227u%I\030\346\254\245Y\215sVB\212P1N\024\341N\247\001O\002\235\2121J\0274\343\210\324\223X\367sy\256@\242\030\260*|b\230\325\013T-P\265{x_\226\205\2175 \\S\306E(52\260\002\227w\2457\2559\0079\247\023\305 \024u4\341\326\245\002\236:R\023P\261\315D\306\243g\307\322\242f\316qUdj\253#\325Y\rWcP9\305W\221\352\271\222\243w8\252S8#\004U1\215\324\222\201\216*\253\270CP4\271\351Q\2310i\215&i\236`\246\2313H_\024\201\367\036h\221x\315@z\323\205(\025i\024\005\367\253\021DO5r8\3609\025&1J\0058\014\323\200\245\002\244\024\352SH9\251\020`\363U/\246\302\355\006\263\242\217sd\325\300\270\024\326\250\332\241j\205\252&\257s\000m\241F\r<\n\\R\342\212QR\216\224\341\305\035M\006\222\236\242\244\024\244\340TL\325\016\356i\214j23PH\330\315T\221\352\254\215P9\250d8\252r\311T\244z\256\322s\305!\220\221\315V\227-U\330\005>\365\013\270\317&\252\315\317CU\217\006\242rsQ\226\3050\265\001\270\246;\322)=ju\223\214\032M\231\351J\027\024\340\274\325\210\306p+B \025j]\330\245\0074\372x\036\224\360\236\264\273pi@\245\244\357\305H\243\271\246\314\373W5\225!2\275O\034{E<\361Q\265D\306\241j\205\252&\257vQ\223O\013\315(\024\340\264\273h\333F\332p\024u\247 \240\365\240S\324S\307\002\230\315P;\324\005\271\241\337\025\031\224b\252\310\371\252\222>*\263\275WiqP\311(#\025FBI8\252\333Y\316)\031\002\216j=\303\322\242\221\205P\231\262p*\264\2101\235\334\325f8\353P\273T,\325\003\311Q\031i<\332\024\356<\324\243\212w=\251\351&\323\203S\207SN,;T\22075y\rH\005J\202\245U\311\346\247\003h\340Uw\224\253\363OY\225\273\323\203\002i\304\201M\363\000\245\r\232\212y21U\343\217\234\232\233\2651\215D\306\242cQ5B\325\023\032\367\244\\S\300\247\005\247\001K\212\\{Q\212gCG&\2348\315%8S\305)<T\016\325]\332\242\335\315A4\265\030|\212kt\252S63T&\227\025L\313\270\343\232^\325\023\020*\274\222\355\351\305Uy3\336\243/\357U\246\233\035\rg\315+)\252\257p\335\352\006\231\215F\\\232\211\336\240\221\352\003%&\363\232\225\036\246\017N\363\005*\266ML\246\245U\315X\210m5q*\302\363S%M\030\313T\300\214\32471\003\326\241\020\205\357FpqR/4\2059\241\334(\250F\\\344\323\372R\023Q\261\250\230\324Lj65\013\032\211\215{\341\247\255I\326\224\nu\024S\010\364\243\030\244\242\234)\340\342\243v\252\3625Wf\250\367`\325y~f\300\245\013\264TR=Q\235\305f\316s\234Ud\030j{6\005V\226N\0175FY@\357U^\340\n\256\367G\267\025ZI\262\0175I\3459\344\346\240\222L\324-!\002\231\347v\246<\234\324\022=W\337\315.\372\2266\315I\273\024\34595f4\316*`1S%L\255V\342l\212\267\030\251\2623R\306y\025ch\340\367\250\2475\016Gz\000\035i\305\325\005@\323\0268Z6\347\2559@\024\214j2\324\3065\023\032\211\215F\315Q1\250\230\327\320\000d\323\302\323\261N\024\242\227\024\224\323I\236)(\245\024\244\340T\016\365Y\336\241f\2463\0002i\261\214\363D\216\000\252\023\313\214\325\007\223uU\221\211\310\2507\001\326\241\232a\214\n\245$\243\234\232\314\232}\316pj\007\220\001\315U\226\340\016\365N[\237z\254\3679\357P\264\376\365\023OL\363rhy\006*\006\2235\021|\032O2\245\212Nj\312\275M\031\031\311\253j\343\034S\303T\252\365*\022\306\264aP\024f\254\007\300\342\234\246\254Fy\025h\0163\351U&\223\3465Y\244\'\245 v\247\034\221\315:5\301\251\030\342\231\272\232\315Q\026\250\331\252&j\215\232\243f\250\330\324lk\350UZ\220\014S\261N\013F\3321HE4\212\214\360h4\235)sLv\252\316\365]\332\242\3150\234\237j\014\201G\025Zy\2532yI\357U\267\032\206I\007j\252\357\305R\232oJ\245+\2229\252\022\270^\365Fk\214w\2522\334\023\336\252<\331\357Q\0313\336\230\362c\275E\277\336\2173\025\033L}i\206Zi\2234\201\351\351&\rYI\211\253Q\271\"\247V5\"\311S\243\325\313c\226\346\264Q\270\247\006\346\245SV\021\352g\230\210\316*\233\266T\223L\006\236)I\245V\346\225\332\230[\212\215\232\243-Q\263Tl\325\031j\215\232\243&\243&\276\214Q\305<{\323\300\247Rb\220\323I\246\226\250\330\212nsE5\233\025\004\217U\235\352\006z\215\237\002\253\313)\003\212\254\327\030\030&\253\3119#\255Ty\200\252\317?\275Wyrj\255\304\373W\255Pi\300\0075F\342\357\260\254\331\256I\357T%\236\252I-Wi)\003\323\035\351\201\263J[\212\201\232\230Z\233\270\323\303f\244S\315Z\216\255FqSn\251\020\346\254\306j\334\'\006\257\243qO\rR\253\324\310\365&\340\303\031\252\316\330R)\241\251\341\250\337J\036\202\364\322\374Tl\325\031jc5F\315Q\226\246\026\250\313S\t\257\243\326\236)\340\323\251\245\2050\2650\275F^\232Z\231\277\024\031*&\222\240w\252\356\365\003\275A$\234UYd\343\255Uy=\352\264\217U\244z\253$\225\001\223\255P\272\233<\n\241#\026\035j\224\307\025Bf\252R5Vv\250\031\351\276e4\276h\337\212i\222\230\315Q\026\2405=ML\255\212\261\033\325\204z\224=L\215Vcj\265\024\234\325\324\223\212\224=H\257\232\225_\024\342\374\361QJ\334\373SCS\203\321\276\224=\033\351\245\3522\364\302\324\302\325\031jajajaji5\364\222\323\3058PZ\243-Q\263Tl\365\031zizc5Fd\250\332J\205\344\250\035\352\274\222Ug\222\252\311-Uy*\254\222\325i%\367\252\317&j\031$\n+:\342A\234\3257\237\216*\234\262\016rk>iG5M\345\252\362IU\313\022h\372\232ajM\324\233\251\214\365\031z@\365*=N\255\221S#b\246V\251C\324\321\275[\216LU\204oJ\265\034\270\340\325\205|\364\251\221\252`\324\245\275)\254r\rB\037\236i\333\3517\322\357\243}4\2750\2750\2750\2750\2654\2650\2654\232Bk\351Q\322\234\r\005\261L-Q\263\324L\365\023=0\2754\275F\317Q3\324FJ\205\244\250\036J\255$\225VI*\254\222UY%\252\222IU\236Z\201\3445VY}\3536\346\343\255gIpMWyI\252\322d\365\252\316\r@\355P\263Te\375i\246Ja\222\220\313M2S\013P\rJ\206\254#T\352\325\"\265H\032\246\215\215[\210\346\255#b\247V\315X\205\271\253j\325 z\013\322o\252\356\330\177\255.\372B\364o\243}!zizazizajijijM\324n\257\245\305\005\261L/Q\263\324L\365\033=F\317Q\263\324fJ\211\244\250ZJ\205\244\347\255D\362T\017-V\222Z\251,\265U\345\252\262KUd\222\253\274\200\014\223Ud\234\023\305S\232oz\316\232\\\346\2523\n\211\210\250\235\352\254\222Uv|\324\016\325\0135FZ\230\315Q\226\305&\352P\325*\232\225jT\253\010jU\251\026\247\216\256F8\251U\252d&\254\306pj\312\275?}\033\351\013\324r\034\256Gj`~)\013\321\276\215\364\322\364\322\364\205\351\273\251\013SKSwQ\272\215\325\364\301jc=F\317Q\263\324L\365\033=B\322Tl\365\031z\211\236\241i*\273\311P\264\265]\345\252\322IU]\362j\274\257\201\326\251\264\234\362j\264\327\n\265\237,\345\317Z\200\275T\232J\243,\225X\311\232cIP\274\225RW\252\306L\032k>j&j\2179\240\324mM\247\255L\242\245Z\225\005N\242\245Z\225jd\253(ML\265e\rJ\255S+\361N\337N\r\232B\324\335\334\021P\206\244-I\276\202\364\322\364\205\351\273\350\335I\272\232Z\223u\031\245\335_K\026\250\231\3522\364\306z\211\236\241g\250\231\352&\222\243/\232\215\232\253\311&*\264\222\372T,\304\324.\330\352j\264\222UY$\252S\314{U\t\'#\275S\222]\307\255B[\025\023\311T\245\222\251H\3715\0136*\026z\201\344\252\362>j\271=i\205\251\245\251\231\2434\207\232LS\324\032\231EN\253S\"\342\246Q\232\225V\245U\251PU\2055*\361R\251\251CT\240\361K\272\234\255C5F[\025\036\356i\013SKSKSKRn\244\335K\272\215\324\233\2517Q\272\224\032\372I\244\250Y\351\205\3526z\215\236\241g\250Y\3522\371\244\006\230\355\201T\245~\265X\266M#\266\005S\226J\254\362U9&\252\222\310\rg\316\370\'\025I\236\230d\250\235\370\252R\234\232\256\347\025]\332\253\273T\016\325\0136j75\0214\204\322P\005.)\300T\252\265*\255L\213S\250\251UjU\024\354T\211S\251\251\224\324\253R(\346\245\307JN\224\3654\255P\267z\2046sHZ\232Z\230Z\232Z\220\265&\352]\324\273\2517Q\272\215\324\273\253\350\306\222\243/L/Q\264\225\013IQ\0311Q4\231\250\313\322\254\237-C$\231=j\274\244\036I\250I\013\316j\264\323g\201T\235\352\254\322\340U\007\233\223U\244\2275VV\3105U\372T\r\221P\263T\016\325ZCU\334\325w5Y\330\212\2179\2460\3154\203M\305\030\240\n\220-=TT\252\242\245U\251\225jUZ\224\nx\025 \025\"\214T\200T\212je9\251\343\\\323\363\203M&\234\247\326\206jc\237\2275S8&\202i\244\323\030\323\t\246\356\243u.\3527Q\272\215\324n\240\267j\372)\236\242g\250\332J\211\244\250ZJ\215\244\250\332J\214\2754\311\201PI)\035*\264\223\037Z\257$\307\326\253\264\231\315@\317U&~\rf\313\'5Y\346\250\232L\324L\325\023\236*\253\266*\027j\256\346\2539\252\356j\026\250\310\240\323M7\024\240S\202\324\212\265 ZxZ\221EL\202\245\013R\001R\240\251\002\324\212\264\360\264\340*D\253qc\024\215L\3174f\232\317H\355\362UV<\320M4\232a4\302i\231\2434f\227u\033\250\335F\352@\325\364+IQ\264\225\023IP\264\225\013IQ\264\225\031\222\232e\250\314\231\250\244z\251$\225Y\244\311\346\241y*\273\313U.%\342\263e\222\2533Te\361M\3633M/U\344\3475Y\332\241v\252\362\032\256\325\013u\246\363IF)6\323\302\323\302\324\201i\341j@\265 Zz\214T\312je\\\364\251Uj@)\340b\236)\330\245Z\261\031\342\203M4\225\024\234S\031\262\225\001jM\324\231\246\223Q\261\246\223M\315\031\243u\033\250\3154\265(j\372\t\336\241i*\026\222\241i*\026\222\2432S\014\231\246\263SK\340T\022IU\036Nj\273\311\212\255$\265U\346\367\252\223M\232\252\3075\013SJdu\250_\345\342\230Z\243v\342\252\310j\026\250Z\240j\214\2126\322m\245\013F\312P\265\"\255H\253R\005\251\024S\361J\005H\253S\3061S\201R\001K\212z\2558-(\034\324\212\010\247S\t\246\026\244?0\250\266\225\353\322\241\221q\310\250\263\212\t\244&\243c\232i4\322i3Fh\335Mg\244\006\234\r{\353\311U\236J\205\244\250ZJ\211\244\250\232Za\232\217<t\250\344\233\336\252I8\035\352\253\334\201P4\331\025ZIj\2735B\347\212\200\232\214\232ij\206S\232\203&\241\221\252\271l\324lj&5\031\024l\365\245\331M+@\024\354R\205\251\002\323\302\201O\013R\005\247\205\247\005\251\225*U\025*\212\220\npZp\030\247\212r\217Zp\024\204`\323\030TMQ\346\235\234\216i\204\216\207\245W\2210x\250\263HM4\232a\246\223HN)\245\251\013SsK\234S\201\257vy*\273\311U\336J\205\244\250\332J\205\244\250\232\\T^v\016MC-\317\275Sy\211\250\231\352?7\007\025\024\217P4\224\302\331\250\211\2461\246\026\250\\\324]MG\"\212\200\212\214\255D\302\230E\030\245\240\212LR\342\224\nz\323\305=EH\005H\005<\n\225je\247\250\251qJ)\300S\200\251\002\344\322\355\305\014*6\024\302\265\023G\3157n)\244S\010\310\301\252\262\251\006\230\016i\r0\323I\246\023L-M\315.isFk\334\032J\256\362Uw\222\241i*\026\222\242ij\027\226\253<\276\365\003I\357Q3\324O.\005W2\363N\336\n\324\016y\246\027\3057~i\214\325\0314\306\351Q7\265D\315Q\261\250\311\24674\314R\355\243\030\240\322\036\264b\236\0058.*e\214\021\301\247l\3059EH8\247T\213R-J\246\245^i\340R\212x\251T\323\210\310\244\333M+L\"\243ja\250\315F\302\242q\221UXm4\302i\244\324l\330\250\213f\233K\2323FiA\257gy*\007\222\240y*\006z\211\244\250\036J\201\344\250\032J\201\344\250^Z\201\344\250\014\234\322\211\251\305\363Q\26574\023Q\223L\3150\324,9\246\021\212\214\323\0174\336\224\271\244\315\035iqF\332z\247\275L\027\326\2342:T\241r9\243m8\nZx\251\026\244\025*\032\230\036)qN\035j@02ju\301^(8\250\310\024\306\025\003\212c\n\214\323\032\2435Ve\346\253\223\212\215\237\322\2429&\222\2234\231\245\315.h\025\353\355%@\357P<\225\013IP\264\225\013\311U\336J\201\344\252\357%B\317Q3\324e\251\233\271\247\2074\355\371\024\231\346\220\2650\232a<\322\036\224\316\325\013\232\214\232ni\t\246\021F)\312)I Rn\346\236\257R+\212\220\032z\276)\371\356)\300\323\251E<qO\006\244V\251\320\346\245\307\024\243\255K!\004\014T\221\034\nV\250\233\212\215\215Fy\2465D\324\303Q\266{Uv;\270=j\264\203\025\001\246\023\212i4\334\322f\212\\\323\201\257\377\331"
+byte_png: "\211PNG\r\n\032\n\000\000\000\rIHDR\000\000\002\000\000\000\002\000\010\000\000\000\000\321\023\213&\000\000\006\nIDATx^\355\334\331\222\343*\014\000\320\251\374\377/_\327\255^&\023\307\361\0166H\347<ug\263A\002\204\235\356?\177\026=\336\037\000\310\3439\005\016\257\217\002?\014\214\340z\t\260j\265\222^\022\200Z\376\016\255.\206\330\\\272NO~\372\010|\231\313!\200l\254\224@\010&\263\334\304\037v\351u?|\346\274M\023\000\000O)J\2433\265#\001H\200W\237\306\374\260\322E\237\336C\034\263\361\235}\242\tki\013\034\021w`\265=\2435#n\002$#\337\001\350\325\236b\344\343k-\202\313\336\373\347c\'\002\000\000\320\250\367]\035\000deM\204-\334\003\200\027\351\006D\272\006\357\224\246\226(\222\010\355\366\326\243\335S\273\330\277\216\360\325z\010\312\330f\003i\002!\030\312\225\005\336B\310\035r\3330\0026\274\204\261X]v[kn;0\000\000\000\255^\r\264U\00463a@h\215\226*\244e\321\201>\030\253\237<\327\324x\335s\331\337\005\014\001;\017\000\310\300\325\025\000\2567\267\203\236{\374\220\242\037\226\200\222\000\340\020\313Mr\022\000\240\030[\022\216YX\214\027\236\242\031C\307\203\277\3373\377\344\266\377Bz\327q\271\314\362\\,\001\000\000\202[.\007\211gs\304\177_\270\371\365\304\"\360\331u\232\001\235\2366\274\221\311\300-\032\276\027\320\360\251\305\262m\001\332\366*\256P\374\336q\351\317\003\000\000\240_\007.\000\034x\013\037T\337\236\377=@\365\003\001\000\274R-\002\375\010\271]*9\r\207\354 \000\000\000\366\262=\034i\240;~O\341\3305\200c\357\2421g\322\360\314{i\310\321\261,\001\202\220\000\000\300\026u\326\376\311_;\037-M\306\312|JD\337=3\354\350\237\367\000\365\244\347s\207jv\214\177\000 \026e@^\027\307\376\342\303\261b\020\221\344\\\034\001\312+=\263X\251j\321\263\000\364\241tm\261\327E+\346E\207\341V\367E\371\276#\323\004\t\200$\310N\002\000\020\304\335\027\010\370L\251\321$\303\005\000\000 \205\347\366\317>\020\000\000\346\204\272\231\025\2521\020\302\337QYit>\367\373\225>\237Rf\003t\356\222\315\373\273g\017CtB\017\274/\t\2045\236\362-\000@\313n\232\243n:\354uj-\372\265>\227NH\200N\010\024@\026f|\000\322\010\177%\347\234\331\356Q,\360\211\274\210hv\032\000 \021\253A^\207b?*\n\037J\304\276\274\306k\030~~_K\203b1^;PGv5e\327\213+(\026@\000 55\005p\237\331\031h\366\211\266\335v\332+\007^yz\352\356\355n5a\033\006\020\307\356E\013\000\350\2225\377\220\323\273\332\323\037\320\233(\211V*pM\365\307\025\'s\3051\340v\022=9\t\000\220\315\317\314\177f\376/\265\275\340\026gBO \3061@\034\026w\000:\365\334\226\254\355O\326\326\272\265\347I\351\353\337#\214eK\224I\007\000\220E\266%o\302\032\010o\014\212\344$@.\3432 }QpP\244Q\023\251-i\t\"\000\000k\324\214\'\331=\323\244\303#\373\360\033\001\240\013V:\000R\033\206\351\327\343\263K\333!i\033\236\335k\340%\001\000\000\267R\220f%\362\367\320\357\000\000@L\253\273\035_\364\017\332\003\2555k5\023/\326\332\371p1\t\000\000@o\036\207vy\t+\337\355M>\324\243tO\334\001j3\323\002\r\331\276= $\t@\033\232\250\216\374\337\204x\232H\254\375d\"\000\000\334\257\323\355\004u\004\332\246\311l\370\307x \227@\213\031@%j\203\276X\3316x\351\244x\371]\240E\005>\242q\001[\370lR\300\266\0010\343\247\242Q\374%7\310\201\254*\307]Q\331\262\237\340\307\215Q\334\226\225Ub\0228\362\247\001}\306\247\317\263\256\356H\002\320\200\207\214\246\016\211\005\345Xbc\020\307\344ZK\200\342\347\023|\345\037\312\367X\020o\201\017\330M\337\033\335\200\355\002\200*\202\327\204\300\275\224\345e\350\307\234\252.\321U?\2746#\"\210b\201,\366A\271\350\266\250D\026\000xuam\320\3656\023\000\000B\272pC\000e5\277\305l}t5}~\315G\267\007Y:\261\351L\006\000`\037\305\335\001Y*\177\026\254\3747\260\346s\344\367\364\233?\317\336|\367\353\320x\307\216On\364\333$\255\233n\0104\313\310IN\002\320\271I9@O\204\357rmM\372\022\340IW\314\3201k\226.\366,<\365k\375\025\177.\2306>\035\340\323c_\346\036\347\3338\240\233\302\013\264i\347\000\336\371r\000\200\306\274U3\212\233#\036\017;f\000\216\262\366\002@\233\336\327\350\367\337\273\366\322\230\274\373\331\363-\017\225\023@\n\347g>\"\350~\375\352\276\0017\323\177\000O\243)Q\241\224\211h\247\246\030b\201\364\000\200\313(\312\001\340(\233W\200:\314\257\000\204aQ\333a\347u\312\235/\007\000\332c9\007\2022\275M\244\332\034\213\377~\361\372,^\213\000\000\000\226\330\005\001\000\344\223\352\346\017\000@V\212>\026I\020\000\210\305\332\016\231Mf\200\305/\202,>\t\000\000\000\320\024W2\000\200\350&\367y\200\210\346\206\372\353\343\377\275\374Lvs\031C\022\022\000\000\000 \231\177\267\305\335 \007\000\000\200\256\270\273[N\207}\331\341)\003\000\000\264\302\226*\013\221\006\0023\305\001\300Yi\2777=\214\352\210\264\335\220\272\351\000\000\000t\306=\201\354\356\276\212!\003\201UwOT\000\000+\354k\000\016\262\337\343\213<\000\000\272\246\230\001\000\200\350T\375\200/\005\364\345w\336n1j\377\003\020\243\377\206\213\235b\021\000\000\000\000IEND\256B`\202"
diff --git a/core/res/geoid_height_map_assets/tile-7.textpb b/core/res/geoid_height_map_assets/tile-7.textpb
new file mode 100644
index 000000000000..83f1fcbf89c3
--- /dev/null
+++ b/core/res/geoid_height_map_assets/tile-7.textpb
@@ -0,0 +1,3 @@
+tile_key: "7"
+byte_jpeg: "\377\330\377\340\000\020JFIF\000\001\002\000\000\001\000\001\000\000\377\333\000C\000\004\003\003\004\003\003\004\004\003\004\005\004\004\005\006\n\007\006\006\006\006\r\t\n\010\n\017\r\020\020\017\r\017\016\021\023\030\024\021\022\027\022\016\017\025\034\025\027\031\031\033\033\033\020\024\035\037\035\032\037\030\032\033\032\377\300\000\013\010\002\000\002\000\001\001\021\000\377\304\000\037\000\000\001\005\001\001\001\001\001\001\000\000\000\000\000\000\000\000\001\002\003\004\005\006\007\010\t\n\013\377\304\000\265\020\000\002\001\003\003\002\004\003\005\005\004\004\000\000\001}\001\002\003\000\004\021\005\022!1A\006\023Qa\007\"q\0242\201\221\241\010#B\261\301\025R\321\360$3br\202\t\n\026\027\030\031\032%&\'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz\203\204\205\206\207\210\211\212\222\223\224\225\226\227\230\231\232\242\243\244\245\246\247\250\251\252\262\263\264\265\266\267\270\271\272\302\303\304\305\306\307\310\311\312\322\323\324\325\326\327\330\331\332\341\342\343\344\345\346\347\350\351\352\361\362\363\364\365\366\367\370\371\372\377\332\000\010\001\001\000\000?\000\364\241\311\247\272\014UR\270j\221E;\024\240R\342\227\024\355\271\244\3074\341\326\234)\340S\302\324\2121O\025\"\324\202\236\265*\324\213R\016\265 \247\221\300\247!\035;\324\312)\342\235\232p4\352\rFx\346\254B\341\206\r<\257j\257 \321\022\0265<\361\205N:\325#\2321IM84\204\n\215\224TD\n\211\361U\334Uw\025\003\212\201\305VqU\334T-P\270\252\322\016*\234\253Ud\025Y\305FV\242\"\241a\232\214\245D\351Q\024\250\235k\327\2323\236)\256\016\334\032\213m8\np\024\340)@\245\3058\nP\264\273i\341i\301i\340S\200\247\201O\024\360*E\251\026\244Z\221jE\251\224\016\365\030 \310qV\024\323\301\247\003N\024\340ii\254)\250v\265[S\221\232c.\346\346\254\300\2358\242\362<(\"\263\366\322m\244+LaL5\033TdT,*\027\250\\Ug\250\030Uy\005We\250XT\016*\264\202\252\312*\234\202\240qP\221Q\262\324L\265\031\024\306\025\013\n\211\205{\010\036\265\024\200T;y\245\013N\333K\266\234\005.)\300S\200\247\005\247\001N\002\224\np\024\360)\300S\305<T\202\236)\336b\257SR\t\227\037/&\201\276C\311\300\251\321v\212\224\032p4\341N\024\341N\024\244f\243#\006\254Dr\274\320\300\203V\255\337\212}\340-\030=\2534\214RQ\212c-F\302\243+Q\270\305@\365]\352\026\250XT\014*\007\025\013-@\353U\344\030\315U\220U9\005VqU\330Tei\2053Q\262\032\210\245D\313\216\265\013\212\205\305z\363q\322\240y0phV\rR\252\322\342\212)\300R\343\322\224\nx\247\001N\305-(\247\np\247\np\247f\202r1\232b\214I\216\306\257\244`\n\225i\340\323\201\247\003O\006\234\r8\032x\247\001HW4\014\257J\221X\265O\037\006\255I\363\304@\254\366\217\232aZLSH\250\312\324o\305Wz\201\352\273\212\210\212c\n\205\326\241+Q2Uy\027\025VAU$\030\315R\222\253=@\302\230E5\216:Tg\232\215\270\252\356j\007\250\034W\255<\200\n\243<\302\2515\367\224\371\315\\\203RW\003\232\224\335\016\306\234\267 \367\245\373H\315J\223\206\251D\202\234\034z\324\313\315;\024\264\003N\247R\212p\245\315;\265F\354W\232\204\334\342A\3175\247\004\333\30758\251\001\245\024\361O\035i\302\236;S\3058S\351\030qO\203\275J\006\rJ\200\223\365\250\245]\246\2414\322)\244Tn*\026\250\034T,*\007\025\t\025\033T-Q3TLj\0075RSTe9\252\216*\273\255DV\232\313Q2Tl0*\273\232\200\324l\265\023\255z4\267\003\007\232\313\270\270\311<\325\tK9\342\230\211*\037\224\232\260\262\314\007SR\255\304\242\247K\306\376!Z6wq\271\001\216+ac\211\324\020\334\3242\304\253\321\251\022M\275\352q>;\346\246W\rN\306zR\342\224\np\247\nQN\002\231*dV;\356[\234\023[V\317\200*\3720\"\244\007\024\361O\024\361\3058\032p4\360i\342\236)q\272\236\213\264\324\246\245\214\322N\205\206@\252\244\021I\214\323J\323\031j\026J\201\326\241e\250\035j\026Z\205\226\241u\252\3561P\265W\220\3259Nj\254\225Y\226\242e\250\310\250\330TNqU\2449\250\030TdS\010\250\334q]K\\\356\025\\\202\346\246H\272T\313\030\251\004c\322\235\345\n_!Oj>\317\267\225\342\246I$A\200\306\247Id\'\3469\0251\215\230d\032\201\244\222>\271\305Y\266\273$\326\224r\206\02584\340i\302\236\0058\nv*)\030(\254\231\030I?\313ZP\214\001V\343r\rZV\315H\0168\247\003N\006\234\r8\032z\232\224\032vjd\0250ZF\351\212lNw\n\270pW\212\253\"\342\241\351IM8\250\236\241l\032\211\222\241d\250\035qP\262\324n\234U9V\2538\252\262UI\005Wu\250Yj\026Z\215\226\253\310j\273\344\324%j6\025\031\024\322*\027\256\221b\247\252\000jA\307Jp85 l\323\301\247n\247\251\315<b\246A\307\322\247\004\355\310\374i\031C\212\245*\030\216S\212\236\326\364\202\003\032\325\216`\303\203S\t*EqR\253\n\220\032R@\352qY\367\327\001\020\205\357Tl\327s\3565\265\020\030\251\024s\305YQ\3005 j~iA\247\003N\006\244SO\006\236\234\232\270\215R\001L~\225[y\215\262zU\350\246WN\265\034\247\232\200\3223\201Q\026\315B\3715\001\342\234\016W\232cTL\265\021Z\205\305T\225j\243\255U\221j\254\213U\331j\026Z\205\226\241qU\235j\026Z\205\226\243aQ\221Q\267\002\253\270\256\246\202h\335F\352p|S\303\323\267\323\325\352Uz\231_<T\253&:R\207\301\241\300qT\336\022\255\225\251\340\270)\303U\305\272\351\315J\267$\036\265f;\241\336\246\373j\201\305V\236\367\336\263\036G\270\177j\321\266\214\240\025\240\215\212\231\033\326\254#v5&y\247\003J\r<\032p4\360i\340\324\261\034\346\255G\324U\2208\246\260\252w*q\305$\014Ur;U\304o1zTrD\303\'\025M\362\247\232\001\310\250\334\324M\355L$\212\214\271\357HZ\243cP\265W\220f\253\272\325Y\022\252\310\225Y\322\253\270\250\030TL\265\003\256*\006Z\205\205D\313L\331P\313\201U\034\327M\272\220\265&\3527\321\276\234\037\232xcN\017R+\324\311%J$\251\267d\0029\247+z\323\260\032\232a\006\221\240*F\r&]i\004\222zT\352]\307Jp\267.~cVa\267\013V\200\333\214S\325\252\302\002}\252\324})\344\323\201\247\212p4\340\324\360\302\235RBy\253\221\236EZV\315;\031\250\244\2175Q\334[\003\221\326\241\267\324B\313\216\325\260\223,\313\322\253\\@95A\270\250\330\324D\340\322\036j6\025\013\002)3Q\2675\013\324\017P8\315U\224b\251\311U\330T,*\026\025\023-B\313P\272f\2421\324R\035\242\250\310rj\006\025\321f\232Z\230^\215\364\273\250\335OY=i\373\307jz\275J\257\212\231d\310\305J\222\021R\254\204\234\324\350\371\025&jD\3062FE&\325\'\216\224\341\020\364\251Q@\343\025&=iA\247\356\243uH\2221 f\257\305#*\362*A \'$\322\203\371R\371\200w\247\007\317\265<\020\335\351\341G\367\215/\314:05$nC|\325z3\337\265ZF\251\224\322\221Y\372\224d\247J\310H\210`En[\202\261\256MYl2\237Z\314\230`\232\254MF\306\224\036)\254*\027\034\032\216\230\325\023{\324\016*\273\325Yj\243\216\265\003\212\205\224\324ej2\265\023\255@\303\025\014\214*\224\315Y\357\'\315Q\264\265\320\026\246\226\250\313P\032\215\364\340\364\340\324\360\324\365z\225Z\245G\305L\255S#\35550q\236*P\334T\261\313\216\243\212\220\225\352*h\361\267=\350i\001n)w\322\356\24058\032r6\r]\023\226@\270\351J\244\324\241\370\245\r\315;u85H\256j@\364\354\325\313yr\274\232\275\033\202*ej\225Z\251j\023\2026\016MS\215G\031\255 \000E\307J3\317\025R\344s\232\244z\324mB\232SP\310j#\322\243j\205\315D\306\242|Ui\024UWJ\256\313Le\250\\b\253\261\305W\222LU)\256\000\351T\244\270&\252\311)j\254\325\023WB\315L-L-I\272\215\324\007\247\207\247\206\247\206\251U\352D|\032\235\037\336\247V\315J\215\371\324\252\365 j\230>\341\322\236\257\371\324\204\250\307\257zqe*1\326\223u8\032pj\221\r^\205\224!\365\251RA\214b\220\236y\2434\240\323\305H\264\360i\340\324\3219C\317J\277\034\230\344T\253p\007=\252\031\365 \200\205\252P\271\231\2131\315]\333\307\025f\026\334\230=\250\'\006\240\270?-S&\243jh84\273\252\'94\302x\250\232\241~*\0065\023\032\211\252\273\340\346\240b\001\250]\200\252\322\275Q\232`\277Z\317\226Fj\254\365\003\212\205\205B\302\241j\333-L-L-M\335F\352]\324\340\325\"\265<5H\034c\336\236\216*Tz\260\262qS\307&\322\rK\346\2069\003\025 \223 T\310\343\036\364\360\370\346\237\36374\241\251\301\251\301\251\352j@jdsVT\232\221NMHq\332\200*@)\343\232P)\375*Tn0E=da\307QJ]\210;\016\rUh\345rwQ\004\255\013\355qZ\221I\270U\210\376\\\232Rj)\233(j\236)\255Q\221\315!\351Q=B\315\212\214\277\255F\346\252\274\233MFe\035\352\026\225}j\027\220\016\365VI@\351U$\230\325Ief\252\257\317Z\205\305@\342\240qP\260\250\036\241j\325-L-L-M-@jp4\006\251U\251\341\251\301\251\352\3252\265J\257S\253\344T\350\330\025*H\000 \363OW\251\225\263O\363\010\030\007\212\221Yvd\236i\003\023\323\245=^\244V\251T\324\311\326\256G\265\207&\226\236\rJ\244S\201\251\001\247\216\264\341\315=jE\247m\311\310\353OV*p\303\"\244\222\3329Wp\024\220\257\226p{U\235\324\205\252\263\271v\300\351JS\002\242aQ54\216*7\351U\332\241`s\315B\344\363\203U_\336\240r\rB@\'\236*\tT\212\254\365]\352\007\252\357P\260\250\034T\017P=B\325\003\n\320-L-M-L-@4\340\324\340i\352j@iwS\203T\252\365\"\265X\215\252\300$u\251\021\206EX\334:c\232Uz\2205.MM\023\200\016ic \223\272\245FS\327\212\224\020:\034\324\310julT\310sR\212z\340S\367\np>\224\360\364\360\324\365j\2205L\2074\346 \nXe\031\332MJ\024~4\355\330\246\034\277\002\200\241hcU\330\344\323\010\250\311\250\230\324-\315D\375*\273Uy\006j\263\214T\017\232\201\311\250O\0078\252\362rs\212\205\233\003\201U\236\241z\201\333\214Uw\250\032\241j\205\252\321jijaji4\252i\304\322\253T\252\324\355\324n\247\007\251\025\252Uj\231\032\254,\234T\210\334\324\301\360\335jM\340\236*Ej\2206i\301\260jEl\234\232x8\251\221\252ej\235\033\246j\326\365\000c\255;u(l\323\201\251\003S\203\323\203\324\201\351\352\3652\2759\233\"\230\215\207\253Fb0iL\231\031\024\365\223\tM\017\232k\034\323\010\342\243cQ6OJ\214\241=\351\214\204Uy3U\3335\003\325w\250^\240aP=@\340\212\254\365]\352\0075\003\232\201\315@\365\013T\017S\223L-M&\232Z\200\324\355\364\241\271\247\207\247\007\247\206\245\006\244V\305J\255R\243\325\200\371\002\246\215\307B)\340\374\330\0257\335\305J\215\305H\247&\244\347\322\234\032\244\014*U\315H\255\216\265:=L\036\244W\355R\003N\337N\337J\032\236\036\234$\251U\352tz\227vEF\371\007\"\246\211\303\'5*\256\345 \036\224\210\340\214\032yB9SI\311\353Mj\211\215D\336\324\336E1\213\n\205\333\332\253\276*&\013\216z\325f\343<Uw\250[\245B\302\241\224n\252\262%Uu\305WqU\334T/P5B\325\013\323\213S\t\246\223I\2323I\232pj]\364\340\364\360\365\"\265I\232z\265N\246\246\rS\241\356:\324\321\311\216\243\232\220\023\336\244V\251\003T\313\'@zT\203\004R\240\311\353S\253\355\340S\367\344\212\2205J\257\212\2208\251\026J\223viwR\207\247\007\247)\251\225\252Uj\234=;9\246\357h\217\003\"\246I\207U\342\225\324\246\037\261\251\221\362:\322\223\336\242f5\033\034\324g\"\232}\350b\010\252\354*&Z\201\205@\342\253\270\250XT\rP=W\220Ui\005V\220Ug\025\003\212\205\205@\342\241aL&\232M4\232L\321\272\2234\233\250\335NV\251CT\212\325(jz\265N\255S+\342\245V\251\327\234{\324\341\370\332{T\212}jQ\202x8\247\364\034\034\323\324\364\317J\221\037i\310\251\003\356l\232~\374\267\024\360\334\324\201\252da\216M<5<585;u\000\324\212\325*5J\257R\253\324\252\365/\017\326\223\312\301\371N)e\220\354\t\327\232\2263\201R\026\342\230G\031\246\026\035\206h\013\270g\275B\352W\255G\273\025\033\021Q7\025\023T\017U\336\240z\201\352\026\250\034Ui\005VqU\234T\014\265\003\212\205\205B\302\253\023M&\232M4\265&\3523HM\000\323\201\251\024\324\252\325\"\265H\032\244V\305L\255S\243T\350\370\251\223,x\251\225\261\326\244\rR\003\351R\253\345pi\350\t\351O\r\212pjr\265J\032\236\036\244W\251\003S\303S\303Q\272\236\032\236\032\246V\251\025\371\251\203T\361\276\0075(j\205\2372\014\366\251\321\352V8\034SKn\030\246\001\212_\247\024\016xj\2574E\016GJ\256y\250\330\324,j\007j\201\315WcP=D\331\250Z\241u\252\356\265^E\252\356\270\025]\305@\302\240qT\311\246\223L&\233\232J\\\322\023E(4\360\325\"\265J\246\244SR\003R\251\251\321\252tl\324\350\307\265L\030\2202jEj\225H\357Rdg\212\221\033\007\"\244$\261\317Jr\343<\363K\236x\247\003R+T\212\325 j\2205;u.\352pjxj\221Z\245V\251\321\252Uo\230\n\262\274\324\027\n\335S\255@\223\310\207\346SV\222\3600\301\340\324\261\271bI\351R\003\232:Q\327\353J\300H\244\036\265I\320\2515\013\212\201\205W\221j\006Z\205\326\240e5\013q\326\241qQ\267J\256\342\253\275Vz\256\302\241u\252\356+<\232i5\0314\334\321\232Pi\017Zu\024\340i\352jE5 5*\232\225ML\206\247F\305Y\211\271\346\246SOB;\323\267b\254\"\202\231\3174\261\261\006\254;p1K\021\014piH\305\001\251\342\244V\247\206\251CS\203S\303R\203O\006\244\rR+T\310\325:\034\221V\221\252P\271\243\310\004\321\366A\235\330\253\n\243n\010\305A(1\237Z\204\315\203\2021NI\206jRy\014\275)\222\000\334\325WZ\201\220T\016\225\013F{T\016\225\t\030\355P\270\007\255V\221qP0\250\331sP:qU\335*\273\255VqU%\006\262w\323K\323KSwQ\272\234\032\235\232\001\247f\224S\305<\034S\301\251\024\324\312jd5:\032\231Z\247V\251\024\323\301\311\251s\267\200sR!\365\342\237\270\376\025&\010\000\323\321\371\245lg\345\247\002@\346\236\017\24585H\036\236\036\234\032\236\255R\003O\006\244V\251\025\252\3025Z\211\363V\220\324\353R\nx\024\025\r\324T\022[+d\342\263\246\267x\330\225\351K\024\344|\257\305M\272\242j\201\205B\342\242\'\025]\352\273\324\016*\027\\\212\256\302\243\"\242qU\244\025ZN\365Y\305D#\311\311\2546U\r\301\310\246\230\201\357BC\273\275G2\252q\236j\024\345\261R\310\2331\212h4\274\216\264\3455 4\341N\3158\032\221ML\246\246SS\241\251\224\324\313\310\310\251\024\324\200\323\324\324\310A\353S#\214`\324\252\343\'#\212L\363\300\342\227w9\2517\226\306iA\247\251\247S\207\024\345lS\303T\201\352Ezxj\221Z\247\215\352\3227q\326\256D\371\025eMJ\246\244\006\226\203\315D\350\017Z\245s\007\004\257Z\253\034\204pz\212y9\250\332\241qQ0\250\034T\016*\006\034T\r\301\250Xu\250\033\203P\271\252\317U\334f\241)\232F\\q\\\256\r4\222)\003\262\3645\033e\2174\344m\224\346\224\2767S\323n\340j\304\221\243.\340qQ\004\\q\326\215\244S\226\235J\rH\246\246CR\255N\206\246\\\232\263\033\014R\347\232\2205=MJ\246\246SN\r\203R\371\271\024\356\n\217Z\010\333@jxjpz~\372pjx4\340jEj\221Z\244V\251\221\252\324OV\342c\273\025q\rL\246\244\006\236\r\031\246\232\212^\225\224\343\023\034S\373S\032\242j\211\205B\342\241qQ\025\315W\2250j\263\212\256\375*\007\346\240aQ\025\246\021\212\255!\346\271\242)\244S\010\244\333M+I\266\224\np\334x\315=r\rNHd\344\363LZ~(\035i\353R\245L\2650\251TT\311\301\342\245\335\223O\006\236\rJ\206\246SO\006\234\r<7\024\273\210\024\375\300\217zvW\034u\247.\336\364\270\317\335\244\r\203R+\324\200\323\201\251T\324\212je5b6\301\253\221\276q\216\265r6\316*u5\"\232x4\354\322\023PJx5\234>g&\236j3Q5F\302\242lTOP\236\rG\"\344UI\027\232\252\342\253\313\305V2`\323K\203U\335\362MW\222\271\242qHZ\232M&h4\001KN\002\237\212Jr\324\202\223\2758T\253S\251\251\243\035\315H*d4\360zT\200\323\301\251T\324\212\325 4\340\324\355\324\271\247\003N\006\237\237JP\304\016)\353\2029\353I\312\232z\275H\247\232\224\032\231\rL\246\245V\253\220\2660j\344g\025aZ\244V\251\003R\356\244-PN~CU\020b\221\2150\232\215\252&5\023TMQ\232i\344Uy\026\252H\265B\343\200k8\276Z\230\362\021ML\221\232d\225\3160\315DA\024\334\321E:\227\212QO\035)*@0\0058t\245\305-=*d\367\253\010r8\251\000\251\024\323\305J*E\305;#\265=MJ\r85874\270=\216i\300\221\326\234\0334\340y\247n\315(<\324\231\365\353H\016\rJ\017\245J\247\212\221\032\247SS+U\270\030\021W\021\252uj\225Z\236\032\227u\005\252\t\233<T$\342\230MFM0\232\215\252&\250\310\250\310\250\311\305B\346\253Hk>\353\2258\254\365\213\004\223PL2\334S\320aj\031k\234ja\250\310\301\246\232ZQN\353J\005<t\244\247\216i\353N\245\3059je5b/Z\2274\240\324\212jE5 4\340i\340\324\200\323\272\032:\322\362:S\203\032xl\323\201\247\003N\006\235\270\223N\316i\352qR\241\'\245H\207&\247SR\253U\250[\006\257#T\352\325*\265<r(-Lg\307\326\242-\334\323\t\246\023L&\230i\206\243ja\250\333\245@\365VW\305Vy*\214\244\263{T\017\307J\252\303-O\306\005V\224\327:\324\303L4\303IN\006\236\r8S\207JJr\324\203\212x\245\245\025*\325\210\317\002\236)\342\236\016\rH\r85<\032\220\032x8\247f\224\032v\352Pi\340\322\346\236\r8\036)A\247\203O\006\236\255R\247&\247\004v\251\024\325\3301\216z\325\244j\260\255R)\251C`\363Li\007\'\265G\273\'&\232[&\232M4\232a\246\323M0\323\010\250\330UyN\005e\\\312rqU\203\356\024\326\305V~j2\270\244n\225Rc\326\271\363Q\232a\246\232i\245\024\341O\006\237\221\212JU\342\245\316qNZu8\nz\365\251\326\244\006\236)\342\234\r8qRn\315<\032xjp4\354\322\346\234\r<\032\\\323\324\323\263J\r<\032vi\353R\243b\246F\343\025<c$\003V\224c\245N\222c\212\264\215\305J\207\232s?5\031$\375(\315%\024\204SH\246\232n3HV\2435\004\215\212\316\270\230\034\200k6f\004\324C\332\220\212aZ\211\352&\351T\346\357XMQ\032\214\232CGZAN\035i\302\226\224\032vx\251\242\0314\3420i\302\236\0059x5*\324\253O\024\341N\025 \247\np4\340i\340\323\301\245\245\025 \245\247\016)\331\245\31585(jxjz\232\231\032\254\304\325r6\350z\325\205\000\266EXSR+S\272\321\212P)qF)\n\323J\323N\0050\232\215\232\240\222P\240\344\326]\335\340\350\reKpOJ\215r\375jP\270\244j\215\215WsQ=T\232\260\232\243j\210\322Rt4\235)\342\234)M(\247\212\222#\206\253.\001\031\024\300*E\024\270\251\005H\225 \245\035i\342\244\024\354R\323\205<S\326\235@\251\001\247\nPih\245\006\224\032x5\"\232\231MXF\253P\276:\325\224|c5eZ\244\006\244\rN\006\234)\331\244\2445\031\2463\001U\345\235W\275g\334_\355\007\006\262\246\277f\316\rSfy\r\002/Z\225W\024\343\322\242cQ3T\016j&5Z^\225\204\325\033Tt\332i\344QJ*U\240\214\032QO\024\364\034\212\262y\300\245e\332i\313N\002\234\005H\240\212\224\014\323\261\306)G\025 \247\203E(4\340jU4\354\321\212u<\032v)(\024\352\0058\032z\232\235\032\247CS\251\351VCg\034\346\255\306\334\n\2305H\2475*\323\300\247b\230N*&\220Uy.@\3175Rk\265\037\305Y\267\027\231\350k9\313\312}\251V\337\035jA\020\024\245E0\323\030\324\016\325\0135D\306\231\236*\274\335+\021\205D\325\031\246\032m(\245\024\354\372R\203\332\235\214S\305H\275j\310q\263\035\350\352i\340T\202\224\n\221jT\031\2470\244\357O\024\340i\324\231\305(<\324\212\324\360j@iisK\232\013\021N\355\223A\342\200iA\251\024\324\252jt5:\266*\302\2779\035j\304.s\212\266\255R\251\251\224\324\242\231$\252\200\344\342\262\256\265UN\024\325\003~\362\0363P\310\362\037Z\200\243\267SJ \035\352A\030\035\250*\0054\212\215\215@\315\212\211\232\240f\250\031\271\2461\250\313qPL\334\032\310j\205\205Fi\246\232E%\024\242\234\016jE\367\251\027\322\245\013\307Zp\253p\306\031\t\364\244\3075 \031\247\005\247\201S(\305!\240S\251\302\235Hi)\342\236)\352i\340\322\346\224\032\0174\354\2221J)wQ\332\234\2652\032\260\225\"\236jp\340~U,r\200\3035z7\315XV\247\371\252\275N*95\024@y\254{\273\366\231\260\235*\232\304\\\345\3715ec\000T\235\260E!@G\035j\"0i\013b\230Z\242f\250\235\252\007j\201\332\240v\250\213S\031\252&n*\274\255Y\306\243aQ\232\214\322\036\224\332(\035i\342\236\265*\212\231E?mX\200\237\272;\323\366\025<\324\200S\200\251\024S\263IE8S\305-6\227\024\242\236:\323\205<\032\\\322\206\245\006\235\232Z^\264\202\236\t\251\227\246jdj\2234\370\316[\025$\352c\034u\244\217P\300\303pEL50\007\025^[\311%\345sP\215\357\367\215J\251\212\225x\247\206\240\2657v)\215%F\315\273\247Z\205\233\025\031z\215\232\241sP9\250\030\324,\324\315\325\023\265W\220\3253L5\023S\r!\246\232J)\342\236\2652\324\350*]\265f\331rH\003\232V\316\356i\352)\370\247\201\232\\`\321@\024\016)\353K\364\245\307\024\224\242\234)\302\226\224\032Zu.i\374\020(\317\006\200i\342\236\246\245\rR\006\247+\340\361VZA*\017QU\236\020\306\225b\003\265I\214\014P8\245\335F\372<\312<\312B\324\306j\210\276*3\'\2551\210\307\025\0215\023\034\324Lj\0075\003\032a5\023\232\255#T\rQ\032\214\323M4\323M7\275-<S\326\247J\260\202\245\002\254\333\022\255\305:C\227\310\247\250\247\342\227\265\004~\224\240\0222(#\201K\214\nQNQ\326\227\024\230\243\024\264\340i\324S\251A\245\245\351K\316y\242\234\032\236\r<\032xjxj\261\033|\206\231\346sN\337\232pl\365\246\027\246\357\244/I\276\215\364o\246\027\246\026\246\023Q\261\246\357\3051\271\372T.\010\252\356j\0265\0315\013\032\256\346\243j\211\2523M4\323M4\332Zx\251\026\246J\263\035N\242\254\333\256\343\216\346\245\222\023\030\311\246-IJ(\31794\240q\301\243\2674\270\247\001J(\245\024\237J\\R\201\232u(\240zR\347\024\341\322\227\255-\024S\201\247\006\247n\245\rOY\010\342\215\374\322\211)\376g\035i\205\350\337H^\223}&\3727\322n\246\226\246\026\246\226\246\023LcL-\236*\031\024\212\200\367\250\\\324,j\007\250\330\324f\2434\303\326\232M#SE8S\205H\265:\n\263\035N\2654Rl>\225l\376\372\"T\222E@\243\006\245\024\224R\212p\346\224S\251\324b\2121E(\245\242\234(\306iz\032ZQK\212(\245\006\215\324n\243u.\357J7R\357\240\271\305\001\350/M\335F\372]\324\233\251\013S\013SKSKSKTmM2z\363PI\355P1\250^\240z\215\215FM0\323\r4\323Z\212QO\025\"\324\311V#\253)O#5j\331\202\251\365\246\377\000\025?<SM&i\300\323\251\300\376\024\341O\300\365\245*G=i:\322\322\342\223\030\245\0034\001\353KI\322\226\234\264\352BqJ\r!\244\315\031\030\246\226\"\215\324n\243u.\3527Rn\243u\033\250\335I\272\227u4\2654\2657u\031\246\223LcQ1\246\026\343\006\241\221p2:T\rP\275D\306\243&\243&\232i)\244\346\212QR-H\005J\265a*\302\032\222\254[\270V\344f\234\347,H\242\220\322QN\006\234\r8\034T\213\3158t\243\003\034R\212\\Q\266\212)GJCE(4\374\322\023M\315\004\346\214R\036)\244\322d\322f\223u;<P\r\004\322n\243u&\3527Q\272\215\324\322i\244\323sHM4\232a5\023S7v=*\031\007\247J\201\252\273\032\215\2150\323I\244&\233\232\005<T\212jAR\255N\225a*A\322\246\204e\205J\343\r\315&i\t\246\320\r;4\340i\300\324\200\342\237\232\\\343\2458\014\232p\030\245\002\220\212L\322\322\036\264\224R\346\202sM&\214\322\203E!\024\302i\204\322\203\232\\\361\212L\321\2323M4\322h\335F\3527Rn\244&\232M!4\322i\204\323\t\250\332\243\'\326\242qU\030\324f\230M4\232i4\231\245\245\025*\324\200\324\250j\302\032\235jQRF\373H\305Lr\347\216i;\322\036\224\323I\232\\\323\201\247\251\365\247\203R\016\224\361\357R\014R\321\320\321\326\223\034\321Hi\010\315\035)3Fi\244\321\273\336\233\236iwzP^\230\315L\245\315\031\2434\240\320M74\322i\244\321\272\2234n\243u4\232nh\3154\323\032\243j\215\2522}j\2214\302j3M4\322h\2434\361OSR\003R\251\253\010j\302\032\224S\207Z\274\233V\023\317&\253\347\232Ri\246\231K\232p4\360i\352x\367\251\007J\220\032\220S\261K\214\321\212)1Hi;SM%!4\322i\245\2517Rf\2234\322h\315\031\367\244\335N\316G\024\252q\326\220\232ajBi\244\322\026\244\315\033\250\315\004\323I\244\315\031\246\236j6\250\215F\325@\232i4\302i\204\322f\214\322\323\201\247\003R)\251T\324\350j\312\032\235iI\305[\265\304\200\2065\034\213\261\310\240\021McL4\200\323\201\251\024\323\324\324\240\361O\006\244SO\006\235J\r.8\246\237jCHi\264\323L=)\244\323OZ\030\00085\036h\315!4\231\2434f\2246)X\372SwqL&\2234\322\324\233\2513Fh\315\031\244&\233\2327PM0\324mQ\232\316&\230M4\232a4\231\245\240S\307Zp\251\005H\246\247CV\020\325\2054\254x\251l\345\304\240\032\275=\271\220\3461\237\245Tu1\360\334T{\263M&\233\232p5 5\"\232\221MH\246\244\006\236\r8\032\\\323\267qIHi\246\222\232i\206\232i\215L&\2234\231\244\2434Rf\220\2327SI\244&\220\232i4\334\321\272\214\322f\2274\231\244&\2234f\220\323\rF\325\226M0\232i4\322i3KN\024\361N\024\361R-J\246\247F\253(\324\342j5b\216\010\255\233=McC\270U\033\253\257>B\325\016\352B\324\231\247\203R\003O\006\244SR\003R\003R\003K\232\\\322\346\2274f\220\323M4\323I\346\230M0\323\r4\323sFh\242\220\360i\271\2434\231\246\223I\232i4\334\321\2323KFi3M&\2234f\214\346\232MF\325\222Z\230Z\230Z\223u\031\245\006\236\rH)\300\323\205H*E5*\265L\257O\337HM cN\006\235\272\220\265\001\252E5 5\"\232\220\032\221MH\r<\032p4\264\240\322\321Fi\246\233Mja\246\232a\246\023L&\2234\240\322\346\220\236)\204\323sM-Fi\t\244\315!8\244\315\031\245\315\024\204\323I\244&\2234f\202i\215X\244\323\013SsIFi\300\323\324\324\200\323\301\247)\247\203O\006\244\rO\rO\017N\rK\232]\324n\2434\240\324\212jE5*\232\220\032x5\"\232x4\340i\300\323\250\315\024f\214\323MFi\244\323s\351L&\230\306\230i\271\2434\240\323\331~\\\212\210\234S\017\037Ji4\231=\251\271\2434QE&h\315\004\323I\2444\231\244\315\033\275\351\t\254&ja4\334\321\232\\\322\203R)\251\001\247\203N\006\236\r8\032x4\340i\301\251\341\251wS\203R\346\226\234\r<S\301\251T\324\212j@i\300\323\301\247\203N\006\236\r.i\t\244\315\031\244&\230M0\232a84\322\336\264\322i\215Q\232\006M(4\340\347\030\244q\305DM0\232M\330\246\226\240\032v}(\315\035h\351Hi3IHM0\322f\2234\026\254\026ni\244\322f\214\321\232p4\360jE4\361\322\236\r8\032p4\360i\300\323\201\247\003N\006\224\032p4\340i\340\323\201\247\203R)\251\024\324\200\323\301\247\203N\3158\032p4\354\321I\2323HM0\232\214\232a4\322i\244\323I\250\311\244\007\035i\336\342\223uI\346\002\270\"\241c\315DM4\232@\330\315(\351\305(4f\2274\271\244&\222\220\323I\246\223M4\334\322f\260Kd\322\023I\272\214\322\346\224\032z\232\221O5(4\340i\300\322\203N\006\234\032\234\032\234\r8\032vi\300\323\201\251\001\247\003O\006\244\006\236\r<\032\22058\032x4\360i\300\323\263Fi\t\244&\214\323\030\324li\204\323\t\246\223M&\230M4\236jEn9\2466G=\251\003qL-M&\230M&i\331\030\343\255.sFOz(\315\033\250\316h\246\032i8\244\246\236\264\206\271\342\334SKR\203FiA\245\006\236\rJ\206\244\006\234\r;4\340iwR\206\247\006\247\006\247\206\247\003N\006\234\247\232\220\032p5 4\360i\340\323\301\247\203O\006\236\246\234\r8\032v\352\\\321\232ni3HNj6\246\023L&\230M4\232i4\302iA\342\244\335\270TG\214\342\230M4\232ni\t\240\032x<\361O\335\232C\3056\2234f\214\322\023M\'4\204\322f\232k\233-I\272\215\324\240\322\346\234\r9NML\r<\032x4\240\322\346\215\324\240\323\203S\303S\203S\203S\301\251\024\323\301\247\203O\006\236\r<\032x4\360\324\360i\340\323\201\247f\234\r\031\2434\204\322f\220\232i4\303Q\232a4\322i\204\322f\214\322\206\301\346\2069\025\0214\334\342\233HzQ\236)CS\325\251\375E34\323II\2323Fi)\264\231\256_4f\224\032\\\323\201\247f\236\265 4\360i\300\323\201\245\315&i\300\322\203O\006\234\032\234\032\236\016jPi\340\323\301\247\203N\006\236\032\236\r<5=MH\032\22458\032vh\315.\352Bi3\357I\232L\323I\246\032\214\323\r4\323s\326\2234\023K\234\214S\032\230M%%&h\3158\032z\2659\271\346\230i\206\220\2323Fi\r!4\225\312f\2274\240\323\201\247\003N\006\244\006\234\r8\032x4\240\322\346\214\373\322\203N\rJ\032\234\032\244SR!\251\001\247\206\247\206\247\003N\rO\rOV\247\206\251\001\247\203J\032\234\032\235\2323J\032\202i3Fi3HM0\363L&\230i\206\233\330\323s\315-\004\323I\374i\207\332\232M&h\2434\003N\rO\rIM4\323\326\212(\244=i\246\271,\322\203N\006\234\r8\034\323\301\247\003N\006\234\r<\032]\324n\2434\271\245\006\234\032\234\rH\rL\r<\032p4\360\324\340\324\340\324\360\324\360\325 4\360i\341\251\331\245\rN\rK\232\001\245\315&h\315&i\t\247.0sM+\270dT\0140y\246\023M\355M\'\2323A4\323M&\233I\301\240\373RQ@4\340\324\340h\246\236i\017ZJPh4\206\270\372p\247\003N\006\234\r8\032Pi\331\245\006\235\232\\\322\356\240585.iA\247\203R\241\251\001\247\006\247\206\247\006\247\206\247\003O\rO\rR+S\301\247\206\247\006\245\31585.\352\\\321\2323Fh\315\006\223v(\017\214\212B\233\206sP\260\3052\230zSM\031\240\234\323M74\032LdqI\232J)sN\rK\234\321M4QE\025\306\212p4\340i\300\323\201\245\315(4\271\247\003K\272\227u.\352\003S\267R\203N\006\236\rJ\247\024\360i\301\251\301\251\340\323\203S\301\247\206\247\203O\006\244\rO\rN\rK\232pjPisF\352\\\373\322f\227>\364f\212m\001\261Ms\232\214\323\r0\232nisHM6\212L\322Q\232L\321\232\\\322\203KE\024\235\351h\2560\032\\\323\201\247f\224\032viA\245\315(4\003K\232]\324\003N\rN\rN\006\234\246\245\rO\rN\335N\rN\rR\003O\006\234\r<\032xj\220585<5(4\340iCR\356\245\315.i3K\2323Fi\t\244&\232M4\232\214\323M4\232L\321\232)\t\240s\305%4\234\321E\031\2434\340h\315.i(\242\270\300iA\247\003N\006\2274\240\322\346\214\322\356\245\315\033\250\315(jv\352Pi\341\251\340\324\201\251\301\251\300\323\303S\203S\303T\201\251\301\251\340\323\301\251\003S\201\247\003N\006\235\232\\\323\201\245\315\031\2434f\227>\364g\336\2234\231\244\3154\232a\246\323M!\244\315.i\r\024\037j@3IHi)s@4\354\322\321E\025\377\331"
+byte_png: "\211PNG\r\n\032\n\000\000\000\rIHDR\000\000\002\000\000\000\002\000\010\000\000\000\000\321\023\213&\000\000\010\244IDATx^\355\335\211r\343\250\026\000\320\224\373\377?9\256\031\333\261\343\310Z@\002\304rN\275z5-K\010.\227EJ\267\363\365\325\266\353\364@\264\313\364\000\000\334\214\263@\034_L\001\240I{\227\300q6\tc\333\233\037\000\000\264\304\356\276\021:\352\240\016\236o.\222\000\000\000\006\322\301C\014\201<\354\301AM\017\242\313y\023\376Y\367\345\227>\030\234\004\340N\"\000E5\275u\206\303\256\327\nV\336\3470<\241&\'\334\362t\225\266\271T\265\336\356S\352\226T\306\302\017\000\3000\376<\365D>\377\337O\216\272\002\242\310\256l\016=\366\036\272\230:\351Tj\362\234\374\345\345\240\254\376\000\371\305\316\265?\347/^\266\370\301\016\347n\000R\266\244s\347v\024\345\\7G\205\\\200\256\254\217\371\365O\2419\326\260\235z\t\\/\355\330i\360\346\177\215\033\201\200w\357K\217\000+\227\354\267p\257_\233\'\354\223\251\330.=bu\375\372\367\236\001\007\003\370H\262\333\377gI\2537\007+\372!w}a0\251\207\350_\006lv\331B\23473\340E\252\001,Z\233\"#\267\0001\247Oo\033s-\211=\202\177\371\372\236\034\237\232v\322\264\023\243\035. \334\322\033\260\321M\373t\206\310\301\235\241\000\260\311TIE\244\343\246\303!\nx\226\370\343\360\r#\224\274W\007\256\361\275\031 C\221\201.2 Z\300\357\364l%\246\263-\271\037\014h$\351\265\2228\225\0216\240\214\214Kc\306\242\367\252\260J\220G\360Fbrb\372A\022\\\223dnw\374mG\371\3737\345\021\250G\220\362\206\352\377\322\237?\244\377\351\235\340d\313[\261:\004\007\2431s}7w\214\332\365\232\241\0041h\233\224\270\333\022\027G\313\316X\021\316\270\'U\221\002\305]oQ\177\306\375\300\032p+\342~\371L\031\227\231c\207L\0373S\227\237Yc\325\335V\313\317b#\177\267\306I*\t\026t\302\210:*\305\274\231\242\014\330 \3152\022\\Ng9/\357}\340\357\216\177\231\331c\366m\305\373\037\312T\243c\367\010\177<\324O\377\274\350-\376i\272\"M)l\272D\364\362\231jI\210&\202u\323LE\003\325\222\000\024\360\231\274\237G\252\325PU[!\244e\375|?\364\364\360\026\335D\016\323o\004\223g\235\233}\340g\034\021#<\342\324t\256\263wm\345a\272\025^\361\035\324S6N\022\240\227|\310\334E\231\213\317\245\321j\327b\272a>\333\376\356\334\177%pH\225\203\257\262\251\255\177\227\023b\036z\303\327y\211S5\364\376\275I\034\306Wy\251\313}\312U\356K\366\033\264`\324\321\000\037\014\006\306r0\343\017^\276\310\322\014\220T\360\264\032|bn\327\327\032SM\225\032\262\024\263\245\343i|\374[\2034\362\224\032\346v\357{\036\236Y\211\302\006j\352\036\231\262\234\212\215\326\345\271\036\3578\323%\274cC\317\243k\227\361\246>\350\313\357\243\364\333\221m5\014\374\245:\334\366\340A\215\240\037:\374\220\370\360-\215\276\342\342\253\016\300\274\364S{\372\022\311\350\325]\326V\302\370\301G_\214\374\341\214\333\345\346.\346\311\214<\262\3155:\214td\023c\371;1\313\377%\003E\246\305\246^\037\265\316\266\315\030\312F\002\314~|\213\374\354\007\231=\357\371\375\347\350\303\341l8\243Ag\t\014VHH\002\213\312b.\0178EH\252P\205T]u\346\310\'\241\310\216L\225?T\241\325\356\214\314Z\226\034H\200\003\227&TG-h\200Ic\215\350\214mi\"\275\347\305\334\267\020\317\035\203\362n\251\273\224\276\344UM\3347+b\276\n\323o\230\372m\031\214\252\245Q]w]\227\227\320\345O\316WwL\233Rs7/\370\255\262\177\250RLOC\256\247\266\000\000df\303\315\017\273h\316\"\367r\272O\362!!\356\352\347k=\265\005\010`\320\307\020\255A,\375`E\002\014h)\031*\325Vm\213\022\0326t\230\"\317&u\330\264\014\032\233\355C\255\356\\\356M\356\263\335\303\3534\237\353\266:\332h\307\336\216\334{\035\365\212\232H%\000\014-j\276(\305\274\224\334RH\257\313\0371\222*\277\267\257\253\277\273\000\000\241r\274\030]+\362\343\263\217\003\347\261\027H$GR\001\355iy*\270\327\335\252\020\353\362w\001h5\200\337\267\212\377\274 \270\264\333\214\363\265\033\271[\036\267[\373\032\264<\375\1775_}\356\222\366b\322\302\022\253\264n}M\241\247\006y3\224\247\326\256{uDw3\t\250\333\301\016\\\272|.;\347\216\245\260T\207x\351JjG\3126\317<k/\024\237+\027\206\260\020\323\363}V\354\363H]\374| \277\231\020\317\034\242\177\277\263A\235\tP\373l\005\344V\347\334D1\022\240\2544\361n\357/P?*\334h\265\017\230\351\357\303e>\335\313NV\0327\257p\212+\000\220X\242\355\305\314\366\222`\211:\201rtYaf\230\206u\327y\377\372k\0221.\022\340T\242\017\230\010`xf\001\350\310\216\367k\346\200\236D\'@\364\005\324\315x\236\032,\"\2035\027\210`~\200\\\306\332O\233K\022\020\304\303\204\020R0\222\000\000\310\256\350\246\263\350\315\200\342\306z\013\013\345\031c\000D\032\355\021l\264\366\206\312\025\227\327\326$\327\r\240\022R\034>\030\026\000\000\205\005\377x \370\304E\037\277O\341{\362g\212y\337w?\376\273\334N|\232\006P\251r\203\002\000\200\363\331\375u\306\223\347\311&\035\240?\006\327P\002X\014``\r\315U\000\225\351b\013\265s\031\350\242\355\275\320\031\033v&\371\207{93\205=;@G0\034IO;d+\274\271\030\021\207<\267\2033\333B\250A@j\0161\t<\246\272\350\246\006\204\217&\374\364d\262\016\215N%\316\225\252\347u|\363R\245B_\306J\354\261Z\233\226\3301\022\353EeL@{\255\246\362xa]\r\307\233\311y\327\351\001\232\264\222\360\037_\361\360i\373\014*\267\222\000\237\364\367P\002\272;\340\024\240I\177Fw\324JA\027\226\246w\271\360\264\024\241\023\235\3319\025\206\343\200\276Z3k%Yv\265~\327E\355\352\247\271+\211\020\255\237\250\214c\265\317V?\234J\231I-k>\016\3157\340d\342\007\264+j\335\357\226y\234\036\254\216\346\225\017\223\344\377\277\351\201v$i\177Y\025V\371Y\245\n\253\306\'\3354\234\253N\007\312ha\262Y\331\025\267\255\205\340\367C\264\233\367\234\t\032~\216\335\257\333i0?\241\013\362\372\327\006\325O\225\325W\020\000\000v\261\323]t\211y\254\21397\271\2147\317V\364\275\340l\245\0374\251\327V5\347>7\260\2327\327\255S!\347\320\255\327(7\334G\'\003\000\200B<\200\320\213\2507nP\234\207\274A\351x\300L02}?\274\200\257L\337\"\213\032\027\233\002:\034:b@\303\270b7\000\r1\265\225\2623\211v^\006\320\201z\226\250\357\235\223\361l\013f\017R\267\235\t\300nUE\274\252\3120(+\307\340$\000@\303\354%\001\000\000F\343u\036\264\307\270\005\362\363\256x0\226\226f\350*r(0\347\027\270\005\014\342{z\240vV\256\204\022|Q\004\0041p\273w\321\3074A\242\002\0000\252\337w@v\3050,/\203i\225\245+\0053\300\227T\202\202\016\016\267\304SV\342\342\032v\260_Z4`\223a\312\034\0100\260?\213\200\025a<\372|`\257\316\257<\013<\261\345u\275\377O\224a8\225\317\375\024\"\017\250\217MI1u\206\272\316ZAG\254\375\300@.\266\026\000\000\000\003\272.<\013z1\006\000\320\232\205\215\035\314\221.0,\303\037I\000\000\000}\332\263\325\337s\r\320\000\203\033\000\000\330\340wb\017L\337\003\000\000@\223<\322\017N\002\000\000\000\000\000\020\311w\n\003\214\3516\377\373\361b\333\364\037\000\000\300P\274\312\005\000\000\000\000\000\200\221\370\213\242\000\000\000\000\000\000\000\000\000\000\000\000\320\013\337\'\010\000\000\243\363T\000$\364\037\213Ab\036\317\"\"\006\000\000\000\000IEND\256B`\202"
diff --git a/core/res/geoid_height_map_assets/tile-9.textpb b/core/res/geoid_height_map_assets/tile-9.textpb
new file mode 100644
index 000000000000..5397cb37fa66
--- /dev/null
+++ b/core/res/geoid_height_map_assets/tile-9.textpb
@@ -0,0 +1,3 @@
+tile_key: "9"
+byte_jpeg: "\377\330\377\340\000\020JFIF\000\001\002\000\000\001\000\001\000\000\377\333\000C\000\004\003\003\004\003\003\004\004\003\004\005\004\004\005\006\n\007\006\006\006\006\r\t\n\010\n\017\r\020\020\017\r\017\016\021\023\030\024\021\022\027\022\016\017\025\034\025\027\031\031\033\033\033\020\024\035\037\035\032\037\030\032\033\032\377\300\000\013\010\002\000\002\000\001\001\021\000\377\304\000\037\000\000\001\005\001\001\001\001\001\001\000\000\000\000\000\000\000\000\001\002\003\004\005\006\007\010\t\n\013\377\304\000\265\020\000\002\001\003\003\002\004\003\005\005\004\004\000\000\001}\001\002\003\000\004\021\005\022!1A\006\023Qa\007\"q\0242\201\221\241\010#B\261\301\025R\321\360$3br\202\t\n\026\027\030\031\032%&\'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz\203\204\205\206\207\210\211\212\222\223\224\225\226\227\230\231\232\242\243\244\245\246\247\250\251\252\262\263\264\265\266\267\270\271\272\302\303\304\305\306\307\310\311\312\322\323\324\325\326\327\330\331\332\341\342\343\344\345\346\347\350\351\352\361\362\363\364\365\366\367\370\371\372\377\332\000\010\001\001\000\000?\000\363<\322\206\247f\234\r(4\271\2434\271\367\243u.h\rK\272\214\322\356\247\006\247\006\346\244\rO\rN\rO\rN\rO\rO\rO\rO\rO\rR\006\247\206\247\203N\rN\006\234\r8\032\\\321\232]\324\271\2434f\214\322f\214\322f\232M4\323I\244\2444\224QE\024\200\3434\235i\r%\024S\250\242\212up\364f\234\r(4\354\320\r.\3527Q\272\2274\003K\2327R\346\224585<5<5<5<585<5<5<5H\032\236\246\244\006\236\r<\032x4\340i\300\323\201\245\006\2274f\2274f\214\342\214\373\322f\214\320M%6\220\365\244\244=i(\244&\200sKM\246\232(\242\212QKE(\024\265\303\323sJ\r;4f\2274n\245\315\033\250\315(j\\\321\2323F\352pjxn)\301\251\301\252@\324\360\324\360\324\360\324\360\325\"\265J\255O\006\236\r<\032x4\360i\300\323\201\245\315;4f\214\322\347\336\212L\321\232\\\321HM%!\024\224\206\222\212i\245\024\264\332i\242\212(\245\024\264\270\245\351Hk\2074\224\231\346\234\r\031\2434f\214\321\2327R\356\243u.\3523F\352]\324\340\324\360\324\340\324\360\325\"\265<5<5H\rH\246\245V\251\003S\201\247\203R\003N\006\236\r8\032]\324\354\322\346\214\322\321I\232\\\321Fh\242\214\323M!\244\242\223\275-\024\230\346\222\233E\024S\251@\245\242\232k\2104\224\206\214\322\203HM&i3Fh\315(4\271\2434f\215\324\340iA\247\006\247\006\247\206\251\003S\325\252@\325 j\221Z\245SR\003O\006\236\r<\032x4\340i\300\323\263N\006\2274f\235E\031\242\224\032Z(\242\220\322\032LqII\336\226\212)\264\204RQJ\005-:\212CI\\I\024\332CIE\024\206\220\322f\223>\364\271\2434\271\2434n\245\006\224\032p4\340i\341\251\341\251\341\252@\325\"\265J\246\245V\251\024\323\301\247\203O\006\236\r<\032p4\340\324\340i\300\320\016)\300\321E(9\245\247QE\024Sh\2444\224b\227\336\222\220\322R\001\212Z)@\245\242\232h\256,\212a\024\323IE\024\332i\246\346\212L\322\346\214\322\346\214\322\203J\r(4\340i\341\251\301\252Ej\221Z\244\rR\251\251T\324\200\324\201\251\340\323\303S\301\247\003N\006\234\r<\032p4\354\320\r.h\3158S\251\324QE\024\204Rb\212LR\320zSh\246\236(\245\305.(\242\212CI\\{\n\214\212a\024\322))0i)\246\233Hz\322Q\2323Fi3N\006\2274\240\323\201\247\006\247\203OSR\003R+T\252\325*\232\221MH\r<\032x4\360i\340\323\201\247\203N\006\234\r<\032\\\322\346\226\234;S\251\302\212)qF)(\"\223\024b\223\024R\032J)\000\305-\024QHM%\025\313\2749\252\355\031\025\031\030\353M\"\230E%4\323H\246\323i\244\346\214\322f\214\321\232\\\322\346\234\r(4\240\323\301\247\203R)\251T\324\212jU5(5 4\360i\340\323\301\247\203N\006\236\r<\032x4\340ii\302\224S\351\324\3521N\242\227\024\224Rb\223\024PFi\264\204RQE\024\202\226\232z\n(\256}\201\035EB\342\241x\3628\250\031\010\250\310\246\221M\"\232i\246\230i\264\207\255%\024QJ\r(4\352Pi\340\323\301\251\024\324\212jE52\232\220\032\220\032x4\360i\340\323\301\247\203O\006\236\r<S\305.iE<S\305<R\323\251qKE\024b\223\024\224SH\2444\224\206\222\212CHh\315\024\231\2435\222\312\r@\360\372UwB\265\031\301\250Y*\026LS\010\3054\212a\024\322)\206\232Fi(\2444\264R\212\\\323\251\342\234*AR)\251V\245SR\n\220\032x4\360i\340\323\301\247\203O\006\244Zx4\360ii\324\341\326\244Zx\247\np\024\264\354QF(\244\"\222\220\212B)\246\233Hi(\2444\224R\032J+0\212a4\326P\303\232\255$\004Ur\204\036j9W\035*\"3Q\025\246\021M\"\230E4\212i\244\242\212)E(\024\352p\247\212x\355R-J\265*\324\202\236)\342\244\006\236)\340\323\305<S\305<S\305>\234;S\207Z\221i\342\236)\330\247\001K\212Z(\244\305%!\024\323M=i\207\255\024\332)\r%\024\332(\252\014\265\021Zk\014Te\210\250\\\346\240e\3109\250\031\010\250\310\250\312\324dSH\246\021M\"\223\024\230\243\024\264\240R\201K\212p\247\nx\025\"\324\253R-H)\342\244\024\361O\035\251\342\236\264\361R\nx\355O\024\361N\024\361OZ\220\nx\024\360)\300R\342\226\2121M#\024\204R\032a\246\236\264\323IHi)\017ZJ):\320\005-Se\250\312\324N8\250XT,\246\242<S\030\217Ja@\325\023\305\351P2z\324M\221\332\223\031\244\3054\212LQ\266\227\024b\235\212P)@\247\001O\002\236\005J\242\244Zx\251\005<S\305<S\305<v\247\212\220S\326\236)\342\236)\353R(\251\000\247\201O\002\235K\212Z)\010\244\246\322\032a\246\032CM\2444\224\206\222\212(\243\025]\205D\302\243e\250Yj\026\250XTL*\"1I\274\212C\206\250\312\343\202*\027\217o#\2450\212M\264\233h\333K\266\215\264\273i\301iB\322\201O\002\236\005H\005<\nx\024\360*AR\001N\002\236\005<S\305<T\202\236\005<S\300\251\024T\200T\200S\305<R\201K\212\\RQM\244=i\246\230i\246\233M4Sh\240\212LP\005\030\245\252\3548\250XS\010\250\331sQ\262rsQ2T\016\230\250J\324l\264\322(\353\301\246\225\364\346\2431\203\315FT\2126\321\266\227m\033iv\323\202\322\205\245\333N\013O\002\234\005H\0058\n\220\nx\024\360)\340S\300\247\201O\002\236)\340S\305<T\212*E\025 \024\361O\024\361J)\324R\021IHi\247\2554\323M0\323i\r%!\024\224QE\024\240T.*\"\274\323\nSJ\212\201\316\r0\340\365\250\23528\252\345i\245i\205j2\264\336E\033A\351HW\324S\nc\245&\332]\264\273iv\322\355\245\333J\026\234\026\234\026\236\026\234\026\234\005<\nx\024\360)\340S\300\247\201N\002\236\005<S\300\251\000\251\000\251\000\247\212x\247\212p\351N\024\264PzSi\246\220\322\032a\351M4\323IM=h\242\212LQ\212\\QQ\311\307QQ\2023\315D\347\346\"\241c\316\r0\25794\205A\250\210\347\035\252\'J\214\2554\255FV\230R\230W\024\017zw\226\010\342\231\266\227m.\332]\264m\245\333K\266\234\026\236\026\224-8-<-8\np\024\360)\300S\300\247\001O\002\236\005<S\300\247\212\220T\213O\035\251\342\234:S\307JQKE!\244\246\322\032Jm0\364\244\246\322b\222\212(\242\212*2\373\206\rDN)\215\202sLe\3151\224\324y\307\024\322\271\2462\344TEqM+\232C\0354\305Lh\361L)M\332iv\322\355\243m.\332P\264\241iv\323\202\323\202\340\323\266\322\205\247\001J\0058\np\024\360)\300S\200\247\001O\002\236\264\361O\025 \247\212x\355O\024\341N\035)GZu\031\246\223\232)\264\206\222\232i\246\233M\242\212m\024QE\030\250\260\001\246\355\004\320c\301\246\225\246\221\305Wd\301\246\036)\206\243jn)@\247b\220\307\232\215\242\305Fc\246\354\366\243\241\301\245\0034\340\224\241)BS\266R\354\245\333N\333F\332\\S\261J\0058\np\024\340)\300S\200\247\nx\247\212x\247\212x\353N\024\360i\342\234)h\242\212)\264R\021M4\303\326\220\322R\021IHE\030\244\305.(\305-T\rRdu\240\270\357L\334\017CLn{\323\0175\023\000)\207\024\306Q\330\3231F(\247\250\247c=E#F\010\250\214x\355Lhw\366\346\232ad\367\251\025r\005<%.\312]\224\273)v\320\026\227m\033iv\322\201K\212v)qJ\0058\np\247\n}<S\205<S\201\247\203N\024\340i\331\245\242\212)1A\024\206\230i\246\220\323h\244\305%\024QE\025L\214\032c=0\266i\003\342\232_\232\003f\230\355Qg4\034\201MV\311\346\236V\224\n\\S\305<-)L\320\023\006\234\321\002:Ub\2066\351\305<.E8-.\3126R\354\243m\033h\333F\3326\322\342\227\024\270\245\002\226\235N\024\352p\247\003N\006\234\r<\032x4\340i\300\361K\232Z(\242\212CL4\323M\242\223\024\230\244#4b\214PE\000R\342\263\013\346\232M0\234S\013S\013Q\277\024\302\364\201\251\373\251\274f\234\r<\nx\024\340\265*\212xZpJv\323\216)\222\306\032>\225]\007j\227m.\332]\264m\244\333F\3326\322m\243\024b\214Q\212ZP)i\324\240\322\203N\006\234\r8S\305<\032x\247\nZPih\243\024\270\246\232i\246\232i\024\224Q\212n1E\024QF+\017\1774\360\334SI\250\330\323sL&\231\234\232\t\305;w\024\3459\247\014\323\303S\325\215L\207&\246\002\244\002\236\005</\024\320\241\216\017z\202X\274\267\247\005\315;m\033h\333I\266\215\264\233h\333I\212M\264b\214QE(\024\264S\251E8S\205H:\323\205<S\251A\245\035i\324S\250\246\221L4\323\322\222\212)\010\346\222\212)1@\353K\\\343\032r\266E)5\0314\332k\032fy\245oZi5\"\032\234t\246\223J\257\212\235d\350EYI\001\353R\214v\251S\223S\252\323Ja\270\244\236=\313\357P \342\244\333F\332B)1F(\305&)\010\246\342\214Rb\212(\242\212u\024\341O\024\361\332\236)\342\224S\207ZQ\326\235J\0058RR\032a\246\232CIE\030\244\"\223\024Q\336\200=)Es&\2054\342i\204\323sMcH)\343\221\212\215\226\225\016*p\374S\013P\032\244Rj\312?\0305:\260\307Z\2327\301\253\321\020\302\234\313\363S\2312\275*\243.\327\305<\n1I\212LQ\212\010\246\342\220\212LSH\244\305\024\230\244\242\212u\002\234)\342\236;S\305<t\240u\247S\307ZQN\247R\021M\"\232i\246\232i1K\212\\sI\212LQ\214\322\025\346\224\016\324\240W.i\005\004\323O\025\0337\245&iA\247\251\024\244dR\005\243\245!\247-N\270\247n\002\234\262U\210\336\257[\311\203VD\300\232\262\010e\025Z\3456\2604\320(\"\223\024b\214R\021M\"\223\024\334R\021HE%6\220\322QJ:R\212x\353N\024\361O\024\361@\353O\035i\302\234)\302\224QM4\323M\"\233E\024\242\214Q\2121HG4Q\326\271L\323Kb\233\273&\232\317\315FM\000\323\363H_\025*6Fi\331\246\223@\245c\201M\363qHe\315H\222U\210\234\325\270\345\"\255\246H\334*\314R\236\206\211\244/\317\245*\034\212~(\305\030\244\"\223\024\322)\010\244\3054\212f)\010\244\246\322\021IJ\0058S\205<S\307Zp\247\322\212p\247\201N\024\340)\330\244#\232CL4\322)\244Q\212Z1K\2121F)\244P\005-q\346\233\324\320\307h\367\250I\246\323\226\245\003\212c\016jU\030\024\356\324\334\323\226\221\307\025\003)\246\200sS\306*\324b\255\306\271\305[\214\225\\T\360\214\346\247\n\010 \365\250\323\344}\265`\014\322\342\214R\021M\"\232E4\2121M\"\230E4\212i\024\322)(\242\224S\3058\nx\247\np\247\nx\247\212p\247\nZ)\010\246\221M\"\233\212JP)h\242\227\024\322)1K\212\343\013f\232[oJc1=i\271\244\247%L(\306M<\nZi\247-\0148\250\217\"\220/52\361V#Ry\253\221\n\266\243\002\254\300\234dT\354\234dT2/\000\216\325,|\201Rm\243\036\324\230\246\221M\"\233\2121M\"\230E0\212i\024\322)\244Rb\224\nZp\247\212p\351O\245\024\360)\342\236)\302\235N\244\305%!\024\322)\244RQE/jZ)\010\244\242\270|\342\202A\2461\246\347\232Zz\361R\nx\247QM<\323\322\207\351Q\nv)\311\311\2558\020\005\025aTT\3123Va;j\354 8\'\265C,xb;S\"\033X\255O\2121HE4\212i\024\334PE4\212aZi\214\216j2)\244SH\244\242\224\np\247\n\220R\342\235O\024\341O\024\361N\024\242\212)1M\"\232i\270\244\242\235J\005\006\222\212+\200-\315\001\251i;\323\205H\007\024\341O\035){P)\017Z\221(z\217\024\247\245I\002\026`kN1\200*\302\324\312*e\025j\014\203O\230g\004Uc\303\203V\227\221F)1M\"\232E7\024\021M\"\232T\0321\306*\006\\\032i\024\302)1E.)@\247\212p\247\322\201O\024\360)\302\236)\324\270\353F3@\024\224\323M4\332\r \024\264\341E\024\230\243\025\347D\363NZx\351KJ*U\247R\203JM\000\322\023\315J\235(\316M&)\247\322\257Z\307\205\031\253\212*d\025aEL\203&\254\'\007\212\224\214\212\254\313\301\366\251\3429QO\305!\024\322)\244Rb\233\212i\024\322)*6\03750\212a\024\230\243\024b\234\0058\np\024\341J\005<\nx\247\212p\247\nQE\024\206\232i\204R\021IE(\024\264\270\342\200(\305\030\2577\034\323\201\247\203N\245\035i\340\323\301\247\nF\342\2054w\247\226\302\323Q\262jZD\\\2775\243\021\342\254\245N\202\245\310\002\244G\253P\363\315X\306EVu\301>\364\260\235\247\006\254\342\202\264\322\264\322\264\334SH\246\021M\"\223\025\031\034\323H\246\021I\266\223\024b\235\212p\024\340)i\300S\200\247\212x\024\340)\324\275\250\305\030\246\232i\024\206\233I\2121KN\002\212P(\305\030\2575\002\236\0058Q\232\\\323\225\252@i\371\305!9\245QK\212\033\221L\037+U\205\344S\200\301\253q\036\225q\010\035iL\276\224\345rjt\255\033q\2003V\2052H\362*\020\207>\365:\036\306\244\3054\255&\332i\\Tl)\204SH\246\221M+M+M+M\333I\2121J\005.)\300P\0058\nx\024\340)\342\234)\300R\322\342\222\232z\322\032i\024\204Rb\214P\005-(\034S\200\245\244\305y\270Z\\b\212B9\2434\341\326\245Zu\024\364\024\204\363N\034\212FL\362)\312\330\353Rn\315O\023\0003\336\245\016O\322\246\217\236\265aMX\213\226\025\245\017J\262\265&2)\0259\351Oh\3062)\000\310\240\2554\214Tl)\204SJ\323\010\244\333M\333HV\230V\220\2554\255&\332\\R\342\214R\342\234\0058\nx\024\340)\340S\200\245\305\024\021M4\322)\270\244\305\030\240\n1K@\024\360)h\305y\2304\354\346\232N)A\315\004S\220z\324\271\300\245\003\024\243\232\223\240\246\232QS\001\305\030\024\030\373\212|C\'\025d\214b\245F\253\0108\311\253p\0163W\2438\002\254#T\312\325\"\363S\371{\223\"\242\t\212R\265\033\naZaZiZaZ6PR\230V\230V\232V\223m&\3326\321\2121@\024\340)\340S\200\247\201N\002\235\212\\z\320G4\204SqHE&)1F\3321F(\305(\024\340)qF+\313\305:\233\324\323\207\024\340sN\024\341\326\234[<S\201\30585\004\322\216\265(<S\224S\311\342\235o\313\223V$\351Dun3\221V\340\340U\3055:\032\225M<?5j\t{\036\206\245e\364\250\272\366\246\225\246\225\246\2244\302\264\233)|\272k-F\303\025\021\300\246\344QF(\333F\332M\264\273iB\323\302\323\200\247\001O\013F=)q\221JG4\322)1I\212L{Rb\227m&(\305\030\243\024\360)qF+\312\305-8R\232\000\251\000\300\240\236\324\243\2123J\032\236\r(5\"5H\r9\217\024\350\016\01754\215O\214\325\250\210\365\253\221\034U\244j\231Z\245V\245\316\rK\024\234\214V\222r\242\230\340\006\351H\303\212M\224\322\264\322\264\334\000ipOALh\237\322\253:\234\340\320\266\254\3434\326\262n\306\241h\335\017\"\220>:\361O\0074\360)vQ\266\235\262\224-8-8-;\003\246h\372R\201\216\224b\220\212LRm\244\305\033h\305\030\244\305\030\245\3058\n1K\212\362\214\322\212~8\245\002\236\006(&\221FNM8\2650\265&\352xzQ\'\255=^\245\022\nz\276jd \032{\216\364\364n*x\233\201W\242|u\251\325\252tz\225[4\254\330\2536\213\274\346\264\320\342\221\316\347\030\243\024\264\306 S0Z\244[|\363R\254[y\247\272\251N:\325#lY\262EZHF\334T2\302\312x\025]\243\335\324Tmh\030t\252\222@\321\036(G\365\253\n3N\tN\331@J]\264\270\243m\024\270\245\000\032B\264\233i\010\244\305*\216\264\230\240\212LQ\212]\264\340(\305\030\257%=jTZy\342\223v(\r\232w\035\351\245\361\322\243/L\336(\337\336\223\314\315H\032\246\215\207J\221\030d\324\350A\251\224`\324\200\3664\231\332\246\247\203$f\256+p*tj\2305L\217O-\232\275b\3406=kQ\024\036\225\033|\214sK\221Mg\364\241\020\267&\254EnX\360*\372[\205\030\"\234b@9\305FD=\360*\264\262 8Z\256\323m<S\326F\223\036\225(\205Xt\346\206\266\300\371j\264\266\233\263\221Td\262 \344\n\215T\241\346\247A\232\227e.\312M\264\322\264\230\243\024\270\245\333AZM\264\205i6\322\250\000\234\322b\215\264\230\243\024b\235\2121F+\3111\315H\274R\232h\031\247\022\0050\275F\315L\'4\302i\246OJE\220w\247\254\303\326\244YT09\247\254\336c\341x\253h\330\034\032\262\262|\2714\261\311\274\373\324\2238\001GsVm[\367d\032\235\0375*\271\035*d\223&\254+S\303\324\360\273\0021[6\363\344\014\365\253C\347\3523A\210t\002\221m\035\317\3355v\033=\277z\254\210\302\014\360\005C-\317e\252\31730\250\031\232\252\276\342i\2715<R\221S\213\254\034T\3510r0qW\002\006Q\300\346\230\326\201\272U9\3541\310\025I\255\3323\2208\247\'5!^)\205i\245i\245h\013K\266\224-\005i1I\266\215\264\230\306i\002\322\355\244\305&)qKE\030\257&\331\212pZk\373Sz\n\214\234\323\t4\302Oz\211\346\003\201P\371\244\236i\371\310\250\217ZQOZz\261C\221W\"\23389\347\275X3\340\343\265Ii \363y5=\311\314\253\212\263\013`}ju|T\252\364\365|\032\230MV#9\346\256F\330\034\325\353yFEn[H\205GL\325\245e\'\370sR\371\321\240\371\216\343\350)>\322\010\302.\rC$\245\270&\253\021\232M\264\322\225\023\307P\024\240)\024\354q\232\221\t\035+F\336^0\335*\364d\036\225ab\0140\302\240\237O\005IQX\3676\215\021\334\005B\215\232v\332B\264\322\264m\243m.\334Rm\244\333AZM\264\322\274\322\343\024\323\3054\232M\324n\245\315\'>\224\273I\257+lRg\216)\204z\324nj\026`*#%D\362Uwj\214\266)\313%?viA\247\006\305(z\221\037oJ\262\254\n\362y\244Y\2126Gj\320\211\314\307y\364\253L\333UO\255>9sS\253\322\371\230\245\216B^\267\355\255\013@\035O\024\004!\261V\243!:\346\256\3013\266\002\212\324\2066#.j\177\335G\313\234\232O47B\007\2654\234\367\243\024b\227\024\322\200\323\014T\337*\236\261\002)D\035\305H\250E_\266\343\025}\010\317<T\340c\336\253\\[\t\201\342\271\313\270\r\274\247\035(\214\206\034S\212\322\025\244\333N\333HG4\025\246\342\220\2121M\300\250\335\200\250\211,x\247\010\031\251\337g#\255 \217\232\221a8\247yT\206<W\224\025\036\224\323\201P\271\250\035\260*\263\022MF\306\241cQ1\250\215 5\"\276)w\322\356\247\006\247\207\251\222BF\321Ly\n\235\244V\345\236\014JE>v\373\253N\210\360*\33251\337\232\265n\200\200Ml\333]\210\343\nNG\245)\273\313p1Wm\246\014>`\010\255{f\207\031R\006:\324\263_\240\033R\253\t\313\036Nj\302>ju\346\237J\r\007\353J\r\024\340\231\247*S\300\301\251\000\366\251\020\340\325\373e\336G5\243\345qQ:\n\310\324\255<\300N+\017i\205\275\252\302:\260\340\322\221I\200)\t\024\231\024\322\342\2432\016\325\031v=\005&\347\364\246\222\347\265\013\003\261\311\253Q[\205\353S\355\013Ma\236\324\301\037=)\341\016)\014f\217.\274\204\212\211\205B\374UW\3115\013qQ\265D\325\013\na\024\323I\232\\\322\356\245\rO\rOI6\266jI\037p\007\336\266l\234yC\236\224\347\270\005\261\217\306\244\216L\324\302LSD\233\237\025~96\000\005Z\215\213\n\265o\t\221\376n\007z\272\322*\r\211NIXp\rL\256OSVcj\265\033\342\254\244\2250z]\364\273\251U\252@i\353RS\200\006\236\242\237\264\324\360\314b\"\265\355\356\026T\301\373\325#GU\346\2040<V%\345\230\311\300\254\326\264pr\271\024\236T\302\233\344\316{R\213Y\217\\\323\276\303.y\3158i\316z\223S\307`\243\250\315Y\032x#!x\2456 \034\025\250\332\311Gjg\223\216\202\220\300~\224\242\020:\322\024\002\220\250\024\224\323M\257\0365\023\036*\274\207\203U\232\241j\214\364\250\332\243\"\243\"\230E&(\305%\024\340iI\251\003.\337z\277gq\225\353\322\234\322\345\252\344-\305L[\212\215$\303\326\234\r\270\014\326\214X\002\255\tx\302\360)\312jej\231\036\254F\365e\036\247G\251\203\232xzxzP\365\"\266jd5.}\351\312\325(5\"\234\365\245#\232\275g(\215\206k\\:\272|\247\232\211\205A%\272\277QP\265\232\366\000\325w\266\307E\240D\000\345)\2050\331\002\246\t\274r)D\002\203\010ZM\341F\007J\202I\262x\246\371\343\0375D\323F\017\024\217.zTe\252\026j\214\275(9\2434\332\362\006\025\013\212\251)\346\253\275DE0\212a\024\302\264\322\264\302\264\322\264\205i\244SH\305(\245\305C<\276Z\222\016)\332d\345\263\223\326\264\201%\271\253\321>\005J_\212\213v\0335\255k\'\356\324\326\2042f\255!\251\224\323\267\372T\250MXF\305YI\005L\263\001R\254\242\236$\247\t(\022T\311%N\222T\242Z\221^\246G\251\224\346\245Q\2322T\3475b+\355\204\014\326\2147K(\031\353S\343\214\212\214\322\034S\n\003Q\030\2114\360\240S\361\212\2574\300dt\252NI\031\rU\244W\316s\232\202\\\2163LRE;y\0244\231\025\02350\265(zv\354\320My\023Uy\033\322\252?Z\205\2050\2554\2550\2554\2550\2554\212a\024\323L\"\232i\264\352\315\324$\302\220*\326\212\204\214\236\225\253\374uj3S\003\305D\347\006\264\355\030\030\226\264\242\224\001\201V#z\260\255R\241\031\353S\254\201{f\227\314$\344S\326CS\243\023S+\021\326\244\022S\303\323\203T\210\325:\275H\262T\213%X\216J\265\033U\225aMs\236\225]\301\007\"\254\332\335la\232\337\212ex\301\0074\034\032a\024\200\322\346\220\220*3(9\025J\347\'\221T\335\231V\242\016O\336\244`\r7g\025\033\014Td\323\r4\232@h\017\212R\365\344\2621\252\316j\026\031\246\225\246\025\246\221M+NX\201\004\261\305Wu\347\212\210\212a\250\3150\323\t\244\243\265f]\246\351\000\255{\030\374\250F:\325\241\367\252t5:\322\262f\255@\031\000\364\253)!\0075r)\263\336\256F\371\251\343 \036j~\033\356\324\253\220=\351y\0075<s`sN\363A\247\007\247\211)\302Z\221f\307z\224M\232\221d\251U\352x\344\305]\212^*e\222\246S\221Lz\205\362\274\325\375:\364\203\265\217\025\246\263\202z\323\314\312\007Z\210\334(\353Lk\324\025]\356\331\363\216\225\037\236Oz\212IK\036\264\306pG5\0162j@\243\034\322\021\201P\265B\324\323\315Fi\271\244&\232My[\324\014)\273x4\302*2)\244Rb\232\335*&\025\023\n\205\252\0264\302i\244\321JzV|\347\367\2035\253f\013F\t\253\241EH\270\251\227\024\375\352*\304rdd\364\246Ip\027\201V-\244\316+J90\005YG\3175b9MXY)\333\363F\352xjxz]\364y\224\tjT\224\325\210\336\255\306\331\251\324\212\263\033U\205z\231d\305!|\232q\033\326\230\243\3139\025g\355g\030\357A\271\'\2754\316i\245\311\245\016i\333\251\214i\264n\300\342\223\314\315!zal\324Li\264\323\315Fx\246\223M&\274\265\2522\264\230\3050\2550\2550\2554\212c\n\205\252\027\250\036\241ja\244\245\002\234G\025F\346>sW4\351\2066\261\300\025\246\010<\002)y\024\354\232P\244\375*O7h\300\252\345\213\311Zv\274\001Z\010\365z\006\335\305Y\300\035)\003\324\210\331\251\001\247\006\247n\244\337@|\323\3075\"\266*\302I\212\263\034\265a$\253\t-X\216J\225_4\375\325\"59\2153\275<c\024\022(\016)\300\323\267SI\246\223M&\230M4\265!4\231\2444\303Lja\024\323^`E \000R0\310\342\243+\212a\025\033\na\250\232\241j\205\352\006\250\310\246\221M\3058\np\\\324\0271\344b\250\020\361\234\212\261m~\361\261\3632kF\rAX\363W\005\302\205\310\002\241{\242N)V\\\212\236\021\223Z1\014\n\262\215\212\265\004\2705o\315\342\220?5\"\311R\211)\302J]\364\273\350\rS#f\235\272\244F\2531\275XW\251VLT\361KV\222Q\353R\t\005J\262S\367\344Rf\205\223\024\374\203M\350i\333\351w\346\215\324\322i\244\323\t\246\346\2234\271\244\242\242q\212`>\264W\231\225\246\221M\"\230\302\243\"\243e\250\332\242aP\260\250\\T,)\204SH\244\333J\026\244\013\212\216E\315Wh\001\250M\267=(0\024<U\310\203\005\000\232q\214\346\247\211=j\324\177)\253\321\276EN\246\246F\305N$\247\207\315<=J\257\305/\231N\363)C\323\203\324\321\2658\2775,oVQ\252Q%<IR\307.OZ\264\254}jt~9\247\2111R\ti\342L\364\245\316i\341\360(\363)\013\322\206\247n\244\335M&\220\232J(\244\006\202qMnj\"0h\2578+M+L+Q\260\250\330sLaP0\250\330TL*\026\025\021ZaZn\3326\323\325i\032\243\306z\323X`PW\214\323H\311\025\"\234S\267\323\225\352\302\034\325\250\016*\342\232\231N)\341\205<0\247\253T\201\2517S\267\323\325\362je\347\255I\274(\244W\311\251\325\361R\254\2250~)C\346\244W*\302\256\307>@\251\204\331\2453zS\322\\\324\352\365 z]\364\240\320[\024\t1N\017\232]\324\205\251\273\251A\247f\226\233Fr)\244\342\230Ni3^zV\232V\243+Q\262\324L*6\025\023-D\302\243aP\262\324ei\214\264\335\264m\243\024\322\271\244\333\212\215\305+\034\001L\003\234\324\200R\021J\2654mW\"j\266\207\"\234d\305\002\\\324\252\365a\017\024\354\321\232P\325\"6*p\331\034SY\215*\275J$\251VJ\235d\342\236\262`\322\274\26543\347\203S\211H\247\2113S#\342\246\022T\202Zw\235@\233=\351|\312O2\234\262{\323\304\224o\245\017OSO\006\2279\244&\243\'\232B\324\302i7W\020R\243+Le\250\231j&Z\211\226\243e\250\231j&Z\211\226\230V\230R\233\262\223m&\3326\323J\324,\274\323\030f\200\265 \024\021J\0059\001\'\212\267\020\351V\323\245)\024\321\301\251\220\324\352\324\360\324\354\321\232p\353\305N\206\225\31538\247\253T\252\325*\275J\0374\355\331\251\"85d7\024\252\325:\311\305<IN\022\342\227\315\245\363h\023R\211sR\007\247\t)\341\251\352jEj\2245.\3523Q\263S\t\315F\317\212a\222\271r\225\033%D\313Q2\324L\265\023-D\313Q2\324l\265\031Jc-FV\232V\233\266\232E&)\010\250XS6\322\355\247SM\024\36485n6\351V\220\346\224\232eH\271\251T\324\200\323\301\247\212p5 <SK\346\220\032x5*\324\200\324\210jPjDlT\241\251\301\252P\334S\303S\267SK\342\215\364\241\251\352\3252\275<75\"\236j`i\331\251\025\251wQ\277\212\214\266i\204\342\242w\250KV;-DW\332\243t\301\250\231*\026Z\211\226\242d\250\331*&JaJ\215\222\230R\232\313L)L+M+L\"\230V\233\266\223\024\230\246\221F)EH\214E[\211\315N\334\212h\251T\324\213\315H\005H\005;\024S\351\273y\247\001O\002\245Zx\251\024\342\244\006\2245J\255N\335\315L\215\232x4\271\246\226\244\335N\rR+T\212j\302sS(\342\234\033\265\005\251U\351\333\3517R\347\271\250\335\352\026j\210\265Qd\250\331j6N9\353P\262TL\225\023%F\311Q2Tl\225\031J\215\222\230R\243e\246\262\324ei\205i\245i\205i\214\2704\322)\204b\214Rb\235\212U\025:\034T\313\'cO\034\324\212*U\251T\324\200\322\356\245\3158\0323NZx\247\347\024\241\251\301\351|\312<\312\225$\251\224\346\246V\305H\032\224\2654\323i\300\323\301\251\243oZ\266\203\214\324\201\261N\006\224\234\324d\340\323\203f\234\010\035i\035\270\342\240g\250\331\251\205\251\205j\"\231\2462\324,\225\033%D\311Q\262TL\225\033%F\311Q2sL)Q\262S\nTei\205i\245i\205*7^j2\264\322)1F(\305(\025\"\324\212\271\2531\245K\267\024\n\220\032p4\341N\006\227u\004\320\254jA%.\362i\273\311\247\253\023R\014\232xZ\225\026\246\034S\267b\234\257\315)j\004\225 `i\331\245\006\244C\212\266\217\305K\270S|\316iZN8\244\031\357J[h\250\314\246\2173\212c5FZ\2235;&j6J\215\222\242d\250\312Tl\225\023%F\311Q\224\250\331*\"\225\031J\215\222\230\313Q\262S\nS\nSJ\324L\265\033%1\226\223m&\332\n\320\026\236\005J\202\254\245I\232\000\3158\nv)\302\235M4\240\323\205\004\322\203N\006\236\rH\246\245SN\335\212Q%;vi\312i\371\315\000T\200T\213\315?\024\341R\253\340T\201\263N\343\326\234\010\245/\212k8\"\240\335\315.\352Bi\231\2435\242R\243)Q\262TL\224\306J\210\245F\311Q\262Tl\225\033%BR\243e\250\331*2\224\302\225\031ZiZ\214\2550\245F\311Q\272\323v\321\266\215\264\233)\301jE\030\251\024\342\237\232@\3305:\020\325&(\002\226\212a8\243u(4\340i\324\3655\"\324\253N\306h\332i\301H\247\250\247\347\024\241\252e\251\007\255H\2434\360\224\275\005*\265\005\350\3631A\223\"\233\272\233\236i\331\244\3154\320\rm\024\250\312Tl\225\031J\215\222\243d\250\331*6J\211\226\242aQ2Tl\265\033-D\313Q0\246\021L\"\233\266\232R\243)Q\262S6Rm\245\333F\332\002\323\202\323\302\323\266\322\025\247\247\006\254\216E8\n\030T\014H4\204\346\201\326\234*E\024\354S\324T\310*P8\243<\324\213Rb\214SM*\324\240\342\244\00752\n\224P\335\351\203\212k5F\033&\235\232M\324f\234\0334\271\240\322W\377\331"
+byte_png: "\211PNG\r\n\032\n\000\000\000\rIHDR\000\000\002\000\000\000\002\000\010\000\000\000\000\321\023\213&\000\000\006\"IDATx^\355\335\333\266\342(\020\000\320\263\234\377\377\344q\315x;G1\367\020\002\324\336Om4\tP\005\001\264\273\177~h\307%=\000\000\000\000\004`G\000\000\000\310\315:\003\000\000\000\000\000\000\000\000\000\000\000\000\000\000\032\345/\013\003\000\000\000\000\324\315>.\000\000\000DdG\000\000\000\000\000\000\000\000\240]~\373\001\000\000\254b\021\001\000\000\000q\\\323\003\204\364\177\036\\\345\002\313\330=\214N\006\000\000t\3114\017\000\242\2623\314\274\213\311bl\022\000b2C\210I\334\001\000\000bJ\326\203v\004[\267r\201\177\013\370\312S\250\232h\002\000\000\000@\\\266\370\001\000X\301\3641\256{\354%\000\000\000\324+\347\277\362\221\363Z\344\347\267\337\214\320s\203\223\000\020\227\376\017\000\221\331%\000\350\226!\036\200\233\322\317\203\322\367#\221\006 }\235\231\315e\000\000\000\200\340\016\336\177\002\000\000\340 \326s\237\242\374\000\"S\3343]\006\000\000\000h\202\235\000\000\000\200p\236\337\241G\371*\2357\266\001\240\t\272*\000\333\230\341C([\273\374\326\363h\335\353?\233\277Zo\004\'\001\242\223\001\301I\200,\314\246\000h\215g\027\000\000\000@\313\354\356\000\000\000S\374(,\2364\346\351k\000\000B0\r\004\010\353\342+\344\310D?8\t\000\000\020\203\255\277\350~3\300\022\000\000\272c\246\307:2\006 \212\333\026\200Q\237\317\034\2601\004\000\315\362\030\007V\353vE\330m\305 \247\367\216\242\323\304!\326\335\261\n\000\000`-s\310\270\304\036\"3\002\000\000DgF\030\214\357\205\203\223\000\020\217\'=\000\360\306\324\240m\342\007\000\360db\024\234\004\240i\333\276\255\333v\326\235\036\323\236#b\266#\205\340|Gt\n\000\200\354rNZ\314\340\033\2645\001\266\2367\355\230\253RJ\215C\200\234\002hC\215\317\020\312\362\314\216N\006\004\367L\000y\020L\032\360\3645\264\3004\026\200\332\205\231c}U\364\353\000\347\021\014\010n|\335\264kx\030\277,\237\216j\251\r\341\033:\345\250\342\0015\032\032\005&\031\"j\366\027\316j\343\264:\343\350\313\241\tp\350\305Y\244\332\241\007z\226\253\343\355\271\316eh\004\23696\3646m\022\313S\355\351\2720+C\202e\270D.F\253\315\036Q\254(\226\034+\355+[B\177;g\313yT\350-\220b\332\274\264{\007P.k\017j\334W\005\376*\222\365F\213/\366\371\301\301-\200\345\336\316.\027\240\266\034\325.k\003\267\366\363\017\327G\361/\267?\354\314\025\010\355\264\356\263q\010z\225\367\275\334\317\361\000\210\313(P7\361\251J\301\'\377\\\3443\025e\3566a\2156L\246\206_\351\25589\013\220\363Zg\030\215\322N\023\355r9\352\236s&\312DnU5\366\2430\037\313\267\267?\347UU\305\3330\030\214\301\203\313\315\237\2763P;O\347\333|\314\026\273G\347~\275#{\375d\016\330!Z-\177\213\215E(\377\235\330\357\370\250<\362\341\370\373\260\322XG\205\355^Yu\371\353\363\217I\301?\333\307\200\3475G\022\366\357pz\203\221\023\342\032i\220\221\303\204\223\366\240\212\205N\332\232\3434X\266\301\203\031\035}\375\316u\333|\335V\354t#-;r\370\347\347\237\364@)\243%\"\261\252\245^_*\255:\351h\203\205\031<\310\200\365-\025z\026\326\201\201\345\372*kN\272\337+G\302\244\327x\177\235\276\307\264{\000s5Z\235?\323/V\250\341\316P\354\366\333\014\027z\205\327\267n\225\327\223#}\007\377\324/c/i\201\316,\014P\232\036\177\256\357\'B\020\022o\204\206i\317\242\230]\236+\277\267\017?W\202\'\016\002\023\267\236x\213E\021\037uo\332\312\332w\2668\373\252\314\237\340-9\233i\255Z\\\261SW\375;,,\366X;,<=\214z\333cc\311\346N\273\275?\367\231V\215%}O\216\036\267\016\276|niq\353\374b\'\217\216\253\266]\232\000[i\3346=\306\303g\364\326\006\361\231<\327\333E\326\236{\214\301ei\372z\314\327\227I\237n\225\374\374\300\324\253Pj\257\372\\\371\026\217\202\237\027\372=m\361\371\375\231k\332>\254\255\345\332\317\323\210\300=}Xo\r\322[}8\214T\201\272\035:\021\233\272\370\324{\034cn@\236{\177\316i\273\243\005\357[\360V\345$\201\337\233\007\257m\263\304\3201j$Rls\037\036\273\034#\013i\275\353\365\036\373\326\343\303N\317\004\350)\017~\353\322S\245\250\305\340b \210\337\252\367\376\\<\303\231y\2253\251\037\227\272\234[\241\r\032+.p4\203B\007\0041<)\000\254d\235\373-c\233\344\\v\316\333R\360\353\343\311\361*g\321\362\226\266\265r[\317k\307\226\314\351[\3771gJ\323\361o\272\360\203\014P\253\364\227\000\265Jfx\265\344iw\t\320]\205*QK\302Nk\243\2244\301P\302\"i\242\224\032\2052\335\'\323ez\244i\356\322\004\207\030\226\016\000\317\317\351(\301I\000\000ZP\366\207\007wK\247T\243N+\362X\311G\217\217\275\001\264\245\374\250CU$\000\363<\362\001\272t\375\367\371C\370e\032~\032\254\250e0\227[\323\274\375\017\022\327\206\243\014l\363\335\355\215\231\000\224\366\3754\002\330\302L\026\240\010\303-\000\205y\364\000\000\204\347\357-\001\000,c\332\004\020\234\357T\000\000\370f\273\200A\022\003\000\200\305\354=\003\000\000\000\000\361\330\031\355\204@\002\000\000\300\371\254\317\001\000\200I\345\376\322\263\345IY\325\265wu\005\nF\373\003\000PX\271\325f\353\252\233\254WW \000\000\000\000\000\000:\344\013E\000\000\000\000\000\250\312\177]O\370\366;\244\233\330\000\000\000\000IEND\256B`\202"
diff --git a/core/res/geoid_height_map_assets/tile-b.textpb b/core/res/geoid_height_map_assets/tile-b.textpb
new file mode 100644
index 000000000000..b04a19403a8a
--- /dev/null
+++ b/core/res/geoid_height_map_assets/tile-b.textpb
@@ -0,0 +1,3 @@
+tile_key: "b"
+byte_jpeg: "\377\330\377\340\000\020JFIF\000\001\002\000\000\001\000\001\000\000\377\333\000C\000\004\003\003\004\003\003\004\004\003\004\005\004\004\005\006\n\007\006\006\006\006\r\t\n\010\n\017\r\020\020\017\r\017\016\021\023\030\024\021\022\027\022\016\017\025\034\025\027\031\031\033\033\033\020\024\035\037\035\032\037\030\032\033\032\377\300\000\013\010\002\000\002\000\001\001\021\000\377\304\000\037\000\000\001\005\001\001\001\001\001\001\000\000\000\000\000\000\000\000\001\002\003\004\005\006\007\010\t\n\013\377\304\000\265\020\000\002\001\003\003\002\004\003\005\005\004\004\000\000\001}\001\002\003\000\004\021\005\022!1A\006\023Qa\007\"q\0242\201\221\241\010#B\261\301\025R\321\360$3br\202\t\n\026\027\030\031\032%&\'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz\203\204\205\206\207\210\211\212\222\223\224\225\226\227\230\231\232\242\243\244\245\246\247\250\251\252\262\263\264\265\266\267\270\271\272\302\303\304\305\306\307\310\311\312\322\323\324\325\326\327\330\331\332\341\342\343\344\345\346\347\350\351\352\361\362\363\364\365\366\367\370\371\372\377\332\000\010\001\001\000\000?\000\310\315-\004\346\212(\245\315-\024\003O\006\234\r;4\273\251sFh\335F\352v\374\014S7SKRn\243}(jP\364\340\364\341%<I\357N\022R\371\264\276m;\315\367\245\022S\204\224\3572\227\314\243\314\243\314\251c\2235)l\212Bx\254\373\221\345\266}j5|\323\213SKS\013Rn\240\265\033\2517Q\272\233\272\224\034\325\210\370\024\354\322n\244-\201Q4\300TfqQ\231\3523=!\270\250\332\350Uv\271$\325\243I\232\\\321\2323Fih\315\024S\201\247\003K\232PisK\232Bizu\246\226\246\226\244-I\272\223u.\3527\322\357\245\017K\346R\371\264\242JQ%H\254M<\023N\334iw\032]\364n\243}H\222b\247\022\344u\240\311\216\365\005\313\007_\245T\007\322\235\272\230Z\232Z\233\272\227u\033\2517Rn\240\232\222>\265eM\014@\250ZP;\325y.3\300\250\014\204\323K\323\013\323\013\324l\365\0235D\317\212\326\242\212(\242\2123K\232L\322\203N\315\031\245\315.isK\232\032L\214S7SKRf\223u!jM\324n\243}\033\350\337F\372pzp~j\302?\025&\372M\364o\245\337K\272\223u(z\221d\247\027\310\252\357)\246+f\224\265F[\024\205\2517Q\272\214\322n\245\rAl\232\2323\201Ry\230\250%\237\035\rViI\357Q\226\315&i\t\2463Tl\325\031j\215\232\242c[tRf\214\321\232J(\315.h\315-\031\245\315\031\245\317\275(4n\3054\2654\2654\265&\3527SKRn\244\335I\272\215\324\233\350\337OS\232v\354S\326lT\202l\322\371\236\364\242J\220IK\276\215\324\233\251\301\351\346L\n\202Bv\223H\207\212R\325\03357u\033\250\335F\352M\324\273\250\335R\tp)\217)\250K\023IHM4\232i4\302\325\033\032\214\232\214\232a5\273A\246\321E&h\315%\024\271\2434\264f\2274f\214\321\232ijajn\352M\324\233\251\013R\026\246\226\243u!jizM\364\365\233\024\246\\\322y\224\242Zp\232\244YI\357R\254\224\361%8=.\352pj\035\270\244V\334\244Ss\216)\245\251\205\251\273\250\335J\032\215\324u\245\346\212Bi\271\244\315!4\204\323I\246\023Q\223Q\261\250\311\246\023L&\272\n\r6\212L\322QHh\315\031\244\315(4\271\2434\243\212L\346\202i\205\251\205\251\245\2517f\220\2650\265&\352izM\364\205\351\205\351\273\350\337K\346R\371\224\273\351C\323\226J\235e\315J\036\236\036\234\036\234$\244y:sJ\222\000iX\367\024\302\324\302i\271\245\315.iG4\361J[\212\215\232\2234\204\322f\222\220\232a5\0314\302j64\302j2i\244\327EHi(\246\321E!4\334\321\232L\322\346\214\322\346\202\324\200\322\026\2463S\t\246\026\243<SKS\013SKSKSK\323\013\323w\322\027\244\337F\372<\312]\364\340\364\242J\231%\305L\262\324\213%?\314\364\240=#>M9^\244W\310\347\2654\2674\322h\315-.}i\333\370\342\223u\033\250\244\315!4R\023L&\230MFM0\232a5\0314\323L&\272J\017Jm\006\233E!\246\232JL\321\2323Fh\315\004\320N)\244\324d\323I\3053=\350-Q\226\246\026\246\026\246\226\246\026\246\226\246\026\246\227\244\337I\276\200\364\355\364\273\351|\312Q-H\263\221\336\247I\263R\254\224\340\364o\346\244W\247\253\340\020)7R\356\245\315.h\006\2274f\224Q\232L\321\326\202i\244\323\t\250\311\246\023L&\230M0\232a\246\232\351h\2444\224\323E!\244\246\322\032J)3@4w\244&\232M0\232c\036\324\204\323\t\246\023Q\263Te\251\205\251\205\251\205\351\245\351\205\351\273\350\337I\276\227}.\372<\312]\364\241\352T\223\336\254$\324\363/\245*\311\236jUz\2326\371I\245\315(4\340is\315.is\3158R\346\220\232\000\240\232i4\302i\204\323\t\246\023L&\230i\246\230M0\327OE\024\204b\222\220\212i\244\244\"\230h\244\244<P\017\024\236\364\204\323I\246\023L\246\223L&\243cQ\226\250\331\2526j\214\2650\2650\2750\265&\372iz7\322o\245\337F\372]\364\340\365\"\275H\257R\253f\246CS\003R\253\343\212x4\360isN\006\2279\247\201KFi:\322\346\2234\322i\204\323I\246\032a4\303M4\323Q\232i\353]0\245\242\212LRR\021\212i\246\232a\246\232)\t\243\266(4\323LcQ\223\232Bj64\302j65\023\032\214\232\211\232\243-L-Q\223M&\220\232ijM\324\233\250\335F\372]\324\360\325\"\265J\246\246SS\306\325:\234\014\232\"$\2615`\032p4\360iA\315H\264\354\322\023Fh\315\031\244&\232M4\323\t\246\232a\246\232i\246\032a\246\036\365\324QK\2121IHFi\r4\212i\024\303L4\206\232i\302\203L&\243<\323I\250\330\323\t\250\330\324Lj&5\0335D\306\243&\243&\232M0\232B\324\322\324\334\322f\214\322\346\224\032\221MH\246\245Z\231jelu5 \334\374t\025:|\243\025(4\360qN\0074\360i\333\251wQ\272\2234\271\2434f\232M4\232i\246\232i\246\236\264\303\326\232i\206\232k\247\245\002\226\212)1IM\"\230E0\212a\246\232h\031j\220\323\032\243cQ\261\246\026\250\313Tl\325\033\032\211\215F\306\242f\250\311\250\311\3150\232c\032a4\322i\244\322\023I\232)sN\006\244PML\213\212\235EJ\242\246LT\240\323\303T\212qO\rN\rN\rJ\032\215\364\273\251sJ\r.h\315%6\233M4\206\232i\246\230i\244S\ru\000R\321E\024Rb\220\212a\024\302)\204S\010\305\"\214sJM0\232\211\215BO4\306j\214\265F\315Q3Tl\325\0335DZ\243-L-L-L-L-L&\220\232Bh\315\002\236\005H\270\025 j\221Z\245V\251U\252Uj\2205H\255\212v\372P\364\340\364\340\324\355\364\241\251\300\323\201\245\006\235\2323HM%%!\246\232CM4\303M4\303]E\024\nv)\010\243\024\224\206\232i\204S\010\250\330Rt\246\223Q\261\250]\252\"j2\325\0235F\315Q3Tl\325\0235F\317Q3\324e\351\205\351\245\351\245\251\273\2517Q\270R\027\002\200\364\242J\220=H\255R\253T\252\325*\275<IR\007\305;\314\247\007\247\207\247\007\247\207\247\006\247\203O\006\234\r8\032vh\315\031\243\024b\222\232E!\024\322)\204S\r4\212\3521F)h\242\212)\246\232i\244S\010\246\021\315Fj65\023\032\205\2175\023\032\205\232\242f\250\231\2526j\211\232\242g\250Y\3526z\214\2750\2754\2754\2757}4\312\0057\315\317J\003\323\203S\203T\201\252Uj\221Z\244\017R\007\247\207\247\211)\301\351\341\351\341\351\341\252@\324\365j\221MH\r<\032p4\340ii@\245\243\024b\232E!\024\302)\244S\010\246\221]=\024R\342\222\212(\246\221L4\302*6\3435\0215\033T.x\250\030\324.j\0265\023\032\211\232\242f\250Y\252\026j\211\232\243f\250\313S\013Te\351\205\351\215%G\270\232z\265(l\323\303S\324\324\201\275\352Ea\232x\223\322\236\257R+\032P\3478\024\375\304S\225\352A%H\032\234\034\346\247V\251\024\324\252j@jAO\024\361N\002\234\005(\024\270\244\"\220\212i\024\322)\204SH\246\021]-\024\242\226\212CA4\224\207\2554\323\010\250$<\324MQ1\250\\\324.j\0075\003\032\205\215D\306\242cP\261\250\035\252&lTL\325\031jaz\215\232\243g\246\023M\3159I\251\001\247\255H\r.\352p$\324\203#\255J\204S\214\224\345\222\245\022\003\326\234\007\245<T\252i\370\364\251\025\252u5*\232\225jQR\001O\002\236\0058\n\\R\342\214R\021M\"\232V\232E0\2554\255t4\240R\321E!4\224\231\244\246\223McU\334\345\252&\250X\324\016y\250\234\325w5\003\032\205\215D\306\242f\250]\252\273\265B\306\242cQ\026\246\223Q\226\246\023M\335\353H95*\2169\247\n\220t\251\025sO\333\266\233\346`\361O\014M.\354Q\27352\014\324\350\265a\022\244\333\353J\023\322\237\202)\313\326\247Z\235*u\251\024T\240S\300\247\201N\002\234\005.(\333F\332iZiZiZa\024\322\265\277E\024\334\321Hh&\222\220\232i\2461\252\354y\250\330\324,j\274\206\241sP5B\365\003\324Lj\0275\003\232\201\215D\306\241cQ\023M&\230MFM&iG\006\246S\232pZ\225H\305L\224\366\\\216*\002\2304\340\330\244\335\232\221Fj\304B\256\304\231\251\200\305<.iB\363R\252\344sM)\203R\240\342\246Z\235*e\025*\212\220\nx\024\340)\300S\202\322\355\244\333HV\232V\232V\230V\230V\266\350\240\323h\246\232)3Hi\244\324NqP1\344\324lj&\252\362T-P\265D\302\241aP\270\250\034Uw\025\003T\017Q1\250\311\246\032a\024\302(\301\245\305J\275*E\346\236\006\rJ\247\025(a\212\211\332\241\316M=x\251\343\346\254\307\305\\\215\300\025 l\232\260\203\212SOC\232\221\223\214\322(\305L\242\246AV\024T\252*@\264\360\264\360\264\360\264\340\264\273i\n\322m\246\225\246\225\246\025\246\025\255|RQHz\322\032m!\244\2444\302j\'<T\016pj65\023\032\205\352&\250Z\243aQ0\250\\Uw\025\003\212\256\342\253\270\250\231j\"\264\335\264\205i\002d\323\304x\245\362\351v\323\325qO\333K\332\230X\212o&\234\006)GZ\265\020\251~\225b%&\255\"\342\246\007\002\234\243uXH\352m\234Sv\324\212\265*\n\260\202\246U\251B\323\302\323\302\323\302\323\266\321\266\215\264\233i\245i\245i\205i\205kO\024\204RR\032Jm!\244\246\232a\250^\241z\214\324MQ\265B\325\033TMQ\232\205\205@\342\240qU\335*&J\215\2435\023Di\236Y\357Hc4\2011N\000\367\245\002\224\nz\2558\256ivf\223\312\2441\342\230i\312\271\2531-YT\315Z\215@\251@\251V2j\314Q\342\255\004\000P\027\232\177\223\306E*\307O\013\212\231\026\254*\324\241i\341i\341i\341iv\322\355\243m&\332B\264\322\265\031ZiZ\277M\2444\230\246\221Hi\246\222\230i\215Q=B\342\242j\214\324mQ5F\325\023\n\215\205D\374\032\205\306j\022\264\206,\216j3\020\035\005D\321f\243x\261P\025\305&\332B\224\233iB\322\204\247\205\247\205\245\002\206\342\241v\250\361\223S\306\265:\220*d5e\rX\214d\325\330\323\212\231V\245U\342\227o5<c\326\234W\024\201jdZ\260\253R\205\247\205\247\205\247\205\247m\243m\033i6\322m\246\225\246\025\246\025\253dRSM6\220\322SH\246\323Z\243j\215\273\324M\322\242aQ\021Q\260\250\330TL)\214*&\025\013\324{\t\240\246)\205i\276]4\245E\"qUY2i\2451M\305&\332]\264\241i\301i\333i\002\320W\212\201\322\232\253S\242\324\351\0215f8qRc\025<\0035\241\030\300\251\325sS\250\247\204\315H\251N\307\024\212\2715aW\0252\255J\253O\013O\013O\013N\013F\3326\322m\244\333HV\230V\243e\251\373\320E0\212B)\010\246\221M\"\232E4\212c\n\215\205B\325\021\250\330TdS\010\250\330TMQ0\250\266n5(@\242\241q\223L\331K\266\242q\212\201\305W\"\243aL\331K\266\227m(Z~\332\002\323\212TL1Q\260\315\013\0375f8\352\322\000*a\216\324\205sV-\327\006\264\020T\350*U\025:\255<\n\220G\271iV<S\302\346\246E\251@\247\252\323\302\323\302\323\266\321\266\215\264\233i6\322\025\246\025\250\312\324\204z\322\032i\244\246\342\220\212n)\244SM1\252&\025\013\n\215\205FEFE0\212\215\252&\025\031\024*c\232k\nn\314\320S\024\302*\027\025\003\255B\313Q\225\244\333I\266\234\022\227e<&i\342*y\217\212\201\342\246\010\252E\212\246T\247\2044\365CR\355\251#\030\253h\3252\275Z\213\232\265\032\346\246\020\232\231#\300\240\2474\241qR\001R*\346\245\013R\005\247\005\247b\227m&\3326\322m\246\225\246\025\2462\320E4\212a\024\224Si\244b\220\361Q\232cTl*6\025\023\n\214\212\214\212c\n\210\212\215\205\"\246\346\251\0311\232\204\2574b\230\302\243e\250Yj&Z\211\226\243)I\345\322\354\245\tR\2549\251\026\034S\366`S\010\244\362\363M1\342\224%H\221\022j\312C\221R\255\267\265I\366ojQm\216\324\361\021\251\022#\232\275\024\\\n\273\004\\\325\321\030\305/\227Ha$dTe1NU\251\225jP\264\365ZxZv\337Z]\264\233h\333HV\232V\232V\243+Qb\220\323H\244\246\323i\r4\323M0\212\215\205F\302\242aQ\260\246\021Q\260\250\310\246\025\247F\270\247\025\250\231)\230\2468\250\215F\302\243+\232\215\222\233\266\223m(\214\232zE\315ZH\270\240\250\024\322*\"\274\323\325i\nd\324\211\025N\221b\254\307\035YH\200\251\2260jO \021\322\233\366nzT\211m\203\322\255$X\025*\361S\246MJ\005H\203\"\242t\346\220%J\253R\252\324\201i\341iB\321\266\215\264m\244+M+L+L+U\351\010\246\221M\"\232i\246\232i\206\233F)\214*&\025\031\024\306\025\031Z\215\226\243\"\2435\"/\002\244\333Q\262\324%i\214\265\036\312\215\222\231\262\220\2453\313\245\021T\213\020\251\004x\247\355\2462\324dSv\344\324\251\036jA\016jd\207\0252\303S\244X\251B\032\22649\253h\234S\304U:CNh\261Dp\222j\332\333\340t\250\331pjh\327\212\215\226\220/5\"\255L\253R\005\245\013K\266\227m\033h\333M+M+M+L+T\361F)\244SH\246\021M\"\223\024\326Zn)\010\2460\250\310\2460\250\312\323Yx\315D\302\243e\250\314D\236\265(B)\300R2\361P\025\250\331j<Rl\315!JB\224\337.\234#\245\013\212~))\215Q\021\232UJ\263\032U\224\2175*\305Vc\213\332\246\021S\2045*E\212\235R\245H\352\312G\201N\362\362jx\240\002\244u\300\252\254\274\323\324`R\025\246\355\346\244U\251\225j@\264\241iv\321\266\227m&\332B\264\322\264\302\264\302\265G\024b\220\212i\024\302)\244SqHE0\322c\212a\024\302*2)\245i\2733Q\025\244\t\223La\264\342\244\333\3057o42\361P\225\246\024\315\'\223I\263\024\306Z\214\212P)M%!\246Q\214\322\210\352E\212\254G\035ZH\352\314qf\255$5*\303O\021R\210\361R\254u2&*`\265\"\'5aW\002\230\353\232\204\245.\332M\264l\247\252\324\212\265 Zv\3326\322\355\243m!ZB\264\322\264\302)\205k?\024b\220\323H\246\021M\"\233\212\010\250\310\244\3054\212\214\2557\024\322)\204SJ\323v\323\014yjy\030\030\246\201JW\212\211\226\230\026\203\214TLj6\250\361F1H\0015 N)\254\265\021\030\245Q\232\260\221\346\254$5:\307\212\235#\315[\212:\270\221\324\242:]\224\361\030\247*T\241)\352\2252GRc\024\326Zf\312M\224l\243m8-=V\236\005;\024\273h\305\030\244\333HV\232V\230E0\255f\342\222\220\212i\031\244\"\230E7\024\204S\010\243\024\302)\245j2)1HV\233\266\233\266\225S\223H\313\3157m\005j6Zc-@\347\025\021\250\230\322\016i\304S\220\n\221\230\001P3f\230FjX\243\315\\\216<U\224Z\231S5f8\252\334q\212\262\211S\254|S\035v\322)\251\000\251TT\250\234\325\205N)\n\342\220\255&\332n\332\002\363K\262\227e(Z\220\n]\264\270\243m.\332B\264\322\264\322\264\302)\214\265\225\2121I\212LSH\246\221M\"\223\024\322\264\335\264\204S\010\246\025\244\333I\266\223m7o5 L\niJn\312\nT.\2705\033t\252\3169\250XTei1K\214\323\225M)\214\232A\001\251\004\0252E\212\262\211R\205\305K\020\346\256 \342\254GVR\246\007\002\243\220f\242PA\253(3S\252\324\310\265:\216(\"\231\266\214S@\245T\251\004y\247\210\263M1b\232\026\234\0058-.\3326\322\025\244+L+L\"\243aYX\346\223\024\230\244\"\223\024\322)\245i6\322b\232V\232V\232V\233\266\215\264l\246\224\244\331\315H\023\"\232\311HR\233\266\232\321f\252J\230\252\314*\026Zi\025\0369\251\2213R\252\n\235c\024\024\024\004\024\270\305I\035ZT\310\247*`\325\210\305[\215ju\247\n~\320EF\311\203O\214\342\254\241\251T\324\300\321\232\\f\232\374p)\025x\247\252\340\324\312\265(Z\0313P\262`\322\005\311\245\000\347\024\360\264m\244\333M+L+L+Q\262\326F(\305&)1HE!\024\233i\245i\245i\010\246\225\246\225\243m\033)Lt\335\224\276VjA\036\0055\243\246\224\250\366sJW\002\251N:\3257Z\201\205&\334\322yx4\365\030\251\024f\246Piv\223K\214R\355\315*\2575z\001\220*\307\227OH\361V\024b\236)\342\2363K\264\232@\270\251V\244S\315YS\221F)\340`SB\344\344\324\212\264\355\265*\212\224\n]\264\307L\212\213f)\3120y\247\355\346\215\264\205i\205i\205i\204Tl+\037\024b\223\024\230\366\244\305\033i6\322\025\246\225\246\225\244+I\262\200\224\355\224\273)\273*E\217\212pJC\036j6\212\231\345\342\243\227\201Y\362\214\232\254\353P\224\247*P\311L\3075*T\312E8\221\212i\346\225EJ\211\223W\240\217\025mR\244\013\212x\024\241jEL\324\311\0259\223\024\300\2315\"\305S,5*\304E=c\251V>\016j=\2704\365\024\375\264\345Z\231V\237\267\212M\264\315\234\323\n\342\244\333\362\203AZaZiZaZ\215\205F\302\261\261F)\270\243\024\230\245\305!\024\230\244\"\232V\223m\033iBS\202\322\354\245X\362jA\035;\313\243\313\250\335@\252\322\034UII5U\320\232\201\2434\317.\232W\024\021\221L\331\315<Fi\353\031\247\2244\251\021&\254,<T\311\026\rZE\000T\302\237R*\323\302T\350\2252\256)\031sO\216,\324\342*\262\221\002)\306,Rm\305!<`S6f\234\027\024\340)\300T\250*R\0061M\333J\0274yt\246<-0\212iZaZ\215\205F\302\243aX\300Q\212B)\247\212i4\224s\351Hr(\006\227\031\245\331J\026\234\022\227e8%H\261\324\201)\257\305B\317P;f\240u\315B\321\324m\025B\321\324f:\212H\361L\013\232<\263\232z\256*E\025(\214R\205\000\324\212EJ\274\324\312*E\025*\214\325\204Z\224-J\242\236\0058.jdZ\235W5b4\342\225\226\230\313P\343$\342\236\005.)B\322\343\024\365\342\2369\247\342\225EH\026\224\247\025\013/&\230E1\205F\302\242aQ\262\3268Z1Q\265\'\226Z\244X\t\352)\342\000)Lb\2436\340\3645\004\260\2249\024\211\351S*\346\237\262\224%8%8GS,t\245p*\274\2435]\226\241\220\005\025_\314\346\224\020z\320\312*\026Ni\236^j)b\342\253\252`\324\342,\212C\r\013\021\025(\030\024\335\244\323\225\rX\215\rN\026\244QS\"\325\205Z\225V\245T\251\002S\325*eJ\231R\254\"\361C\n\215\207\025\010\0304\354Q\212p\024\340\264\354S\220sR\020\0059EH\203&\236W\025\023\256~\265\t\025\033\n\211\205F\302\243aX\324c4\242.y\251\200P0\005\033I\351A\030\035j2i\t\305!\001\3075\031\267\364\247F9\301\353S\005\247\205\245\331\212@~lU\205Zd\202\2532\346\242\223\n*\224\274\325r\244\032L\322\026\"\243g4$\274\363S\262\007Z\254a\301\247\205\300\247*\344\324\242.)\246,R\010\375\252d\216\246\010\005;mH\261\346\247H\352\302\307\305=W\025:-H\022\235\214T\211S\245Y@1Lu\246\021\305BW\232\\R\355\245\003\024\360)H\342\235\030\317J~9\245\305H\215\203R1\365\2460\250\030sQ\260\250\330TL*6\025\214\027\361\251Q=i\342<\363I\267\007\245\014\307\030\250Y\252\"rh\353R\'\002\244\002\232@\317J\225E<\n\\S#\\\271\253J\274Tn*\007\030\025J^M@V\242t\250Yi\204TL\264\314\02552\271\3059>c\203S\371y\024\320\2305f5\004S\214Y\240AO\021b\236#\245\021\324\250\230\251\221j\302\247\024\204`\324\321\221R\323s\223S\306\001\024\376\2254oRu\246?J\213\024\001K\212\\P)\325,k\201Hz\323\2513\203S!\336>\224\036\265\033\016j&\025\023\n\215\205B\302\263\025*M\240\n:\212a5\033\232\205\2174\312z\322\223\216i\301\370\342\234\274\324\252\265(ZR\274S!\341\315Y\003\212\216A\200MRv\'5\013.j\"\265\023\255DV\243d\250\212\323\nf\200\270\247\257\006\256Dw\n$\\sI\023`\342\257F\273\205K\260\n<\274\323\204F\234\"5\"\305O\n\027\255J\035@\250$q\232X\344\251\374\314\212n\376j\304r\020)\373\362jd\247\356\244\334qL\357O\024\242\234\005.)@\251\001\3055\251i\010\247+\025\351S*\356\346\221\222\242d\342\240qP\260\250\330U\000\270\240\340Td\322\032\205\315B{\323i\340P\307\212E<U\204\025:\212~8\245\347\024\221\240\337\232\230\374\242\243s\221U\0359\246\024\250\331*&Z\210\255F\313P\262\3236\322\354\243mM\017\006\255\024\334\265\017\225\264\325\230\344\332*_754L;\325\215\353@\221hi@\351P<\271\351M\014\306\234\020\236\265*\307\212\220%<GR\252\201\326\234\000\365\251\024\343\245J\017\024\204\322\nx\247S\200\247b\227\024\264\032@9\305-8\n\236#\306)\314*\'\025Y\305B\302\242aT\rF\306\243cLg\342\241&\232i@\247\342\232\343\"\210\200<U\225\\qR\255<\032ZU\340\346\234[ \324f\230\302\242e\250\231j&Z\210\212c-D\311L+I\217j6\323\227\203\305Z\211\370\301\251\366\006\246\371T\341\035H\024\216\224\034\322\000\324\340\204\323\304t\274\npjQ!\025\"K\353R\356\310\310\245\334M9sS(\342\236)\373sJ\006)@\247\201N\002\234\005;\024b\227\024\322)@\247/\314i\377\000t\361O\316E1\371\025Y\352\026\250\232\263\230\324Lj\026j\214\234\323M&9\247\216\00586i\257\351P\243lz\274\247#5 jpjZp4\200\201\301\247\210\375i\256\230\250H\250\330Tl*&\025\021\024\302)\204Sv\321\266\227\024\345\342\246Y\rI\346R\211i\302Zp\2234\360\364\340\374Q\346\032n\354\321\232PsOZ\221IS\355S\251\317J\225EL\265 \024\340)\330\245\002\234\0058\nx\024\340(\305\024\204Q\212T8jRrh\017\212Fj\201\315@\324\306\254\206j\211\232\242c\232BqM\242\236)qM5\024\243\034\323\240\270\354j\322I\232\2247\255;9\247\nF\007\250\247\307q\3742\017\306\236\314\276\265\001\031\246\260\250\231j&Z\215\226\243+M+L\"\214Q\2121N\024\360)v\323\200\247\201R\001O\035(\002\214S\266\322\205\247\201R\001OPGJ\235:T\353R\250\247\201N\002\234\026\234\026\234\026\234\026\234\005.\3326SJ\321\212n(5\031\342\230Z\230y\250\3150\326\031j\215\2156\220\232J3J\r8\032By\250\2449\025\014C\347\253\253\362\324\352jA\355N\006\235\232f\321\232v\320p{\322\201\305#.FEF\313Q\262\324L\265\031Za\024\302\264\334R\342\214R\201O\002\234\005=P\323\302\323\200\247\250\247\205\030\240\256)@\247\001N\002\244\002\236\005H\243\0252\324\313R-<\nx\024\340\264\340\264\354T\2423\212i\\Q\212n)\n\323J\323H\250\315F\302\243\246\032i\256|\232a4\332BqH[\024\302\364\241\351\333\250\r\232k\232\205N\032\255+\344T\361\265J\017\024\360iA\247u\245\035)\312)\330\3151\226\243e\364\250\231j&Z\214\2550\212n)1K\212P)\300S\224T\352)\373h\333J\005<\n~(\331J\026\236\0058\n\221EH\005H\242\245Z\221jP*@)\340T\201iv\340\212pbh#\024\230\244\"\232E4\212k\n\211\205D\302\243\"\230i\206\271\322i\244\322TO%D_4\205\351\003\342\235\346P$\346\234[5\033t\342\235n\371m\254j\370\030\251\025\251\340\323\3058S\324S\205<R5V\226P\247\024\335\331\355HT\032\215\226\242+M\333M\333K\266\227\024\001N\025\"\234T\201\251\302\234\005H\253O\013N\333K\266\224\nx\025\"\212x\024\345\251\205H\242\246QR(\251Uj@\270\245\003\232VQ\214\212i\311\034\322Rb\223\024\204S\010\250\331j\026Z\211\205FEFk\234\244<T.\335\205Ws\3150\2750\275&\372\013R\253\363\315J\257RpED\312U\262\265b+\223\300z\270\2370\342\245\034S\325\252AO\034\323\305>\232\325\237u\301\247\301\312\214\324\333\007jk!\250\231)\205)6\322m\244\333F\3321N\247\212\221MH*E\251\005-8R\201N\003\322\234*AR(\251\005H\2652\324\350*eZ~\332\220\240\t\232\217\024\302)1F)\r!\024\322)\214*\027\025\013-D\302\242a\\\331\353Lv\300\252\254\325\0136j3M&\233\2327S\263R+T\310sO\333L+\203\232\225.\031:U\313k\201\'\r\326\257\252\251\035\205\007`\007\232\024\203\322\244\006\224\265C$\241G\275R\226O9\300\035\005Y\215p\005J)OJaZaZaZiZM\264m\244\305.)@\247\212x5\"\232\22058\032vi\302\236:S\2058T\253R\255J\242\245Z\235\rN\206\245\003=($\3644\303L4\224Q\214\323\274\2763Q\021Lj\211\205B\302\242aP\260\256d\232\2573UGja4\302i\204\323I\244\31585H\246\246\215\271\253jF)\255\3157fjH\243 \344U\261+\001\201Mb\344pi\251<\221q\326\245K\341\2347\024\367\277\215W\206\346\251\231\344\270l(\300\253pC\264s\326\254\201\212Z\0014\204\2323\352(\3004\322\264\233(\331I\345\321\262\215\224\270\245\002\226\236\016)\352i\342\245QR\001N\013\351O\002\236\0075\"\324\212je5*\232\235MH\255\212R\324\302i\231\346\226\212T\344\325\223\235\234UV\0305\031\025\023\n\205\205D\325\013\n\345\\\340U)\033&\240&\230M4\234\323M4\322P*E\251\220\324\353%.\374\232\225Njej\225\006j\\\niAPI\000jbY\202y\253\321@\2508\025>1K\364\240\nZ1\232]\264\340\202\227`\240\306)\273\005&\3126\322l\3154\255\030\305\030\315.)\352*U\025*\212\225V\244QO\013\232v\302)\3123O\003\024\365\251T\324\252\325 j\\\322SM(4\3602)\007\312r*_;#\232\205\216M0\323\030T\016*\026\025\013\n\343fz\250\315\232\214\232a4\224\206\232i)@\247\003OSR\203J\t\006\247F\251\224\214\325\204z\225H\247\022(\306i\350\2650\030\034R\321\214\322\205\247b\214R\201N\002\234\005.)\010\244\305&\3326\322\024\246\354>\224m=\305.\332r\255H\253R\250\251TT\252\265*\257\2558\014PW\373\264\003\353O\003\322\234)\340\323\324\323\203R\203A\244\006\244CO#5\031\244=i\244S\032\242a\232\205\205D\302\270)\0375\t4\302i\264R\023M4\224\023@4\3655\"\265J\246\244Z\231ML\206\246\006\236\274\324\300qO\035)\324\352v)@\247b\224\np\024\270\247\001K\212\000\245\332)\245Gj\002\203K\264\nLz\322\021\352(\013N\tO\013R*\324\252\265\"\373S\305;u\031\346\203\203\365\241\01685(\347\2458S\300\245\242\235\326\234\200g\2321\203\305;4\322i3Hi\206\243j\211\205B\302\274\355\215FM4\232m\031\244\315!4\334\321@\247\212z\232\231\rL\265(\036\225\"\261\251\326\246Z\220\032x\247\212p\024\361N\024\340)\300S\261@\024\354R\342\212Bh\244\2434\023FE\002\244\002\236\277J\220})\340\372\323\263F\352\\\322\346\224\032p9\353N\\\212\231H>\306\236\005?\024\252\007~izg\024\001A\024\224\206\222\220\232i\246\032\215\207\245D\302\274\321\2150\232i4\231\367\244\315!ji4\231\245\006\234)\342\236\265*\n\235je\251\224T\252*E\251\224T\200S\200\247\201O\002\236\005.)pi\324\240R\321Hi)3F\352JL\322\322\203N\r\212\221\\T\201\251wR\203N\315(4\271\245\315(jz\265J\0105*\277@\177:\224\032x\366\245\002\220\320y\024\322\010\246\322u\244\244\"\230E4\212k.k\313\t\246\223M&\232M!4\231\244\242\234\0058T\200T\212*e\0252\n\231EL\242\246QR\001R(\251\005<\nx\024\360)\340R\212ZZ3Fi3Fi\t\246\223I\2328\307\275\007\2123\212\001\247\003\232^\247\212\220\034S\303S\203S\201\247Q\232\\\321\232p8\247\253\324\253&jU\223\037J\225^\244\017\232RE&\352BsM4\235\250\372\323M\030\246\342\232Ey94\204\323i\264\332\r\024\240S\300\247\201R(\251\024T\310\265:-L\242\245QR\250\251\024T\252*@)\340S\300\247\001O\002\227\024b\203HM4\232L\322\023I\232L\321\272\214\323\272\320\t\034v\241\207qJ\275)\303\245<\021J\r<S\201\247R\216\264\264R\346\200i\301\252E\222\245W\251\003\323\304\236\264\273\2517Q\272\214\363\305\005\251\013Rn\244$SI\025\344\331\244\244&\232M%\024\240S\300\247\001R\001R(\251\225jeZ\225EL\242\245QR(\251TT\212*@*@)\340S\261K\214t\245\006\203M&\232i\244\322f\233\232\t\244\2434\231\247\003N\004\366\247\014\343\236iW\234\346\227\214P)\300\323\305<S\2058R\321E\024S\263N\r\212\221^\236\036\224=.\3727R\357\245\335I\272\232M!jaj\362\274\322\023M&\222\212P)\300S\300\247\201R(\251\024T\352*U\025*\212\225EJ\242\245QR\250\251\000\247\201O\002\236)sE\031\246\2264\026\3154\232L\323sIE!4\206\200sJ\r=i\331\305\024\2434\341N\247\003O\006\236\r8\032Z)GJR(\244\"\200is\212P\364\355\364\273\350\337J\036\227u\033\350\335A4\302k\313(\'\024\332)E-<\nx\024\360*E\0252\255L\242\245QR\250\251\024T\312*U\025\"\324\202\236\005>\2274\231\245\315&i3IHE4\212L\322f\215\302\223\"\223\255\030\305/\326\224\023N\025 \245\247R\212u8\032p4\340isN\024\242\224\n\\PE2\227&\214PM&qF\352\003R\357\240\275 zx|\322\023_\377\331"
+byte_png: "\211PNG\r\n\032\n\000\000\000\rIHDR\000\000\002\000\000\000\002\000\010\000\000\000\000\321\023\213&\000\000\003,IDATx^\355\335\335r\263 \020\000\320\214\357\377\314\231\357\247^\264:QQ(\260{\316]\2155\215\254\013,\306\276^\324\360\336n\000\000\000\000\242R\006\000\000\200r\306\321P`\331n\210@\026\270+d8\000\000\000\000\300\240\324\343\000\000fb\035\032\000\000\000 \253\307\353zJK\220\316\343\274\001\000\000\000\000@\037\n\274@\t9#\261\326\215\357F\003\000\000\000\"2\337\375\311\371\000\230C\353z0\000\320\231\311\331=\261\007I\261?\035\000\000\000\337\230\002\002\000\000@\036ow\010\000@J\326\002 )\343\377\334\264?\000\000\000\300\324\324\366\001\000\000 0\367u\000\000\000\000\000\000\000\300)\313\353y\371N\00506Y*=!\000\000\211x\252?\000\000\000\307L\034\271\302\322\002\000\000\000\000\000\000\000\000\000\000\000\304\347\373\003\000\000\000\000\000\323Q\332\005\000\200\346\026\343n\000\000\000\000\000\000\000\306gu\033\000\000\000\000\000\000\000\000\350\316\215L\000\000$b\370\233\331{\273\001\000\000\000\000\000\022\361\317|\000V\026\0169\262\215\217\355\317\3718\003\000\000\000\000\000\000\201Y\014\312\300MC\214\357N\224\272\037\016\000\000\000Z0\341\006\000\000\000\202Q\356\000\000\000\000\210F\305\007\000\000(\342\271\032\344 \322\001\000\000B3\355\003\370D\206\004\000\200\021\271\343\233j\004\023\3004\244l\000\000\332\263>\014\000\000\220K\257\325\207\306\357kz\013<\3208C\001\000\300\2573\306\005\000\000\340\216O\363\311\375r\354\317-\333\337\333\357?\276\345\257\355\266i\254\'\374\376\007(j\261\363\2679\337\003\310K\206\000\350\257h\360\307\320\264e \006I1]\276Hk\004@\215cp\317\345\206\006\000\000\000xB\021\002\200\036\364?\311\t\000\340\036\353\327\305$\\\000f\243\273\007\000\010\242h`W\2643\000\014M\257\266\223k\251B\000\000_r\345\276\007\244\315\272v\201\267\333P\231\006\344\221\377\001\324:J\031\334[\004\260\022\n\311\t\000\200tL\'!%\227>\000[\372\006 \275\2654\232\257B\252\007\000\000\000\000\000RQ\024\005HNG\000\337\344[ \377]\303\236\337\257?\314\327I\233\321\325\000\000\00000\005\201\030\224\037\000\370Hg\237\233\366\207C\023_\"\2139\000\'&\016o\036\221\034r8\270\302\017^b<\325\233\253\372\001\211L\2171\213\253\027\266\026\315\355j\234\000\323\223\356os\352\206\246#\003\000\200r\2469\311M\033\000\255g\200N\0140\245i\223\027Uh\177`X\022TX\0361GWM\002\260\3051y\350\270\0279~\025\200n\364\251\000\3008L\035\353i5\312ku\\xI\001\2759\377\364!\362\206\241\217ON\000\000\000P\235\t\037+\217\320\316N\000d\367/\002N\352\016\313\351\036\244 \n !\027>@R:\000\000\200F\024\3449d$\016\000\361U\350\357+\034\002\200N\344p\000\000\000\000\000\000\000\000\000\000\000\006\362\007Wv]\t\272\351Z\n\000\000\000\000IEND\256B`\202"
diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml
index 37220ed27286..f5e30095f871 100644
--- a/core/res/res/values-af/strings.xml
+++ b/core/res/res/values-af/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Stemboodskap"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"Verbindingsprobleem of ongeldige MMI-kode."</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Kenmerk word nie gesteun nie."</string>
<string name="mmiFdnError" msgid="3975490266767565852">"Bewerking is beperk tot belbeperking-nommers."</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Kan oproep-aanstuurinstellings nie van jou foon af verander tewyl jy swerf nie."</string>
<string name="serviceEnabled" msgid="7549025003394765639">"Diens is geaktiveer."</string>
diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml
index 49a0b7abbe86..305cefa22455 100644
--- a/core/res/res/values-am/strings.xml
+++ b/core/res/res/values-am/strings.xml
@@ -28,6 +28,8 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"የድምፅ መልዕክት"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"የተያያዥ ችግር ወይም ትክከል ያልሆነየMMI ኮድ ባህሪ።"</string>
+ <!-- no translation found for mmiErrorNotSupported (5001803469335286099) -->
+ <skip />
<string name="mmiFdnError" msgid="3975490266767565852">"ክዋኔ ለቋሚ መደወያ ቁጥሮች ብቻ ተገድቧል።"</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Can not change call forwarding settings from your phone while you are roaming."</string>
<string name="serviceEnabled" msgid="7549025003394765639">"አገልግሎት ነቅቶ ነበር።"</string>
@@ -395,54 +397,30 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"መተግበሪያው የራሱን ክፍሎች በማህደረ ትውስታ ውስጥ በቋሚነት የሚቀጥሉ እንዲያደርግ ይፈቅድለታል። ይህ ለሌላ መተግበሪያዎች ያለውን ማህደረ ትውስታ በመገደብ ስልኩን ያንቀራፍፈዋል።"</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"የፊት አገልግሎትን ማሄድ"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"መተግበሪያው ፊት ላይ ያሉ አገልግሎቶችን እንዲጠቀም ያስችለዋል።"</string>
- <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
- <skip />
- <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
- <skip />
- <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
- <skip />
- <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
- <skip />
- <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
- <skip />
- <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
- <skip />
- <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
- <skip />
- <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
- <skip />
- <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
- <skip />
- <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
- <skip />
- <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
- <skip />
- <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
- <skip />
+ <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"የፊት አገልግሎትን በ«ካሜራ» ዓይነት ማስሄድ"</string>
+ <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"መተግበሪያው የፊት አገልግሎትን በ«ካሜራ» ዓይነት እንዲጠቀም ይፈቅዳል"</string>
+ <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"የፊት አገልግሎትን በ«connectedDevice» ዓይነት ማስሄድ"</string>
+ <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"መተግበሪያው የፊት አገልግሎትን በ«connectedDevice» ዓይነት እንዲጠቀም ይፈቅዳል"</string>
+ <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"የፊት አገልግሎትን በ«dataSync» ዓይነት ማስሄድ"</string>
+ <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"መተግበሪያው የፊት አገልግሎትን በ«dataSync» ዓይነት እንዲጠቀም ይፈቅዳል"</string>
+ <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"የፊት አገልግሎትን በ«አካባቢ» ዓይነት ማስሄድ"</string>
+ <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"መተግበሪያው የፊት አገልግሎትን በ«አካባቢ» ዓይነት እንዲጠቀም ይፈቅዳል"</string>
+ <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"የፊት አገልግሎትን በ«mediaPlayback» ዓይነት ማስሄድ"</string>
+ <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"መተግበሪያው የፊት አገልግሎትን በ«mediaPlayback» ዓይነት እንዲጠቀም ይፈቅዳል"</string>
+ <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"የፊት አገልግሎትን በ«mediaProjection» ዓይነት ማስሄድ"</string>
+ <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"መተግበሪያው የፊት አገልግሎትን በ«mediaProjection» ዓይነት እንዲጠቀም ይፈቅዳል"</string>
+ <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"የፊት አገልግሎትን በ«ማይክሮፎን» ዓይነት ማስሄድ"</string>
+ <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"መተግበሪያው የፊት አገልግሎትን በ«ማይክሮፎን» ዓይነት እንዲጠቀም ይፈቅዳል"</string>
+ <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"የፊት አገልግሎትን በ«phoneCall» ዓይነት ማስሄድ"</string>
+ <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"መተግበሪያው የፊት አገልግሎትን በ«phoneCall» ዓይነት እንዲጠቀም ይፈቅዳል"</string>
+ <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"የፊት አገልግሎትን በ«ጤና» ዓይነት ማስሄድ"</string>
+ <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"መተግበሪያው የፊት አገልግሎትን በ«ጤና» ዓይነት እንዲጠቀም ይፈቅዳል"</string>
+ <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"የፊት አገልግሎትን በ«remoteMessaging» ዓይነት ማስሄድ"</string>
+ <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"መተግበሪያው የፊት አገልግሎትን በ«remoteMessaging» ዓይነት እንዲጠቀም ይፈቅዳል"</string>
+ <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"የፊት አገልግሎትን በ«systemExempted» ዓይነት ማስሄድ"</string>
+ <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"መተግበሪያው የፊት አገልግሎትን በ«systemExempted» ዓይነት እንዲጠቀም ይፈቅዳል"</string>
+ <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"የፊት አገልግሎትን በ«specialUse» ዓይነት ማስሄድ"</string>
+ <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"መተግበሪያው የፊት አገልግሎትን በ«specialUse» ዓይነት እንዲጠቀም ይፈቅዳል"</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"የመተግበሪያ ማከማቻ ቦታ ለካ"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"የራሱን ኮድ ፣ውሂብ እና መሸጎጫ መጠኖች ሰርስሮ ለማውጣት ለመተግበሪያው ይፈቅዳሉ።"</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"የስርዓት ቅንብሮችን አስተካክል"</string>
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index 1db09f1a06a0..dc8ffda3e6ff 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"البريد الصوتي"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"‏حدثت مشكلة في الاتصال أو أن رمز MMI غير صحيح."</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"الميزة غير متاحة."</string>
<string name="mmiFdnError" msgid="3975490266767565852">"تم تقييد التشغيل لأرقام الاتصال الثابت فقط."</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"يتعذر تغيير إعدادات إعادة توجيه المكالمات من هاتفك أثناء التجوال."</string>
<string name="serviceEnabled" msgid="7549025003394765639">"تم تفعيل الخدمة."</string>
@@ -2320,8 +2321,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"يتعذّر الوصول إلى كاميرا الهاتف من على جهاز <xliff:g id="DEVICE">%1$s</xliff:g>."</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"يتعذّر الوصول إلى كاميرا الجهاز اللوحي من على جهاز <xliff:g id="DEVICE">%1$s</xliff:g>."</string>
<string name="vdm_secure_window" msgid="161700398158812314">"لا يمكن الوصول إلى هذا المحتوى أثناء البث. بدلاً من ذلك، جرِّب استخدام هاتفك."</string>
- <!-- no translation found for vdm_pip_blocked (4036107522497281397) -->
- <skip />
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"لا يمكن عرض نافذة ضمن النافذة أثناء البث."</string>
<string name="system_locale_title" msgid="711882686834677268">"الإعداد التلقائي للنظام"</string>
<string name="default_card_name" msgid="9198284935962911468">"‏رقم البطاقة ‎<xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-as/strings.xml b/core/res/res/values-as/strings.xml
index 9a0dbfad02b6..8f2e3f215f7b 100644
--- a/core/res/res/values-as/strings.xml
+++ b/core/res/res/values-as/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"ভইচমেইল"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"সংযোগৰ সমস্যা বা MMI ক\'ড মান্য নহয়।"</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"সুবিধাটো সমৰ্থিত নহয়।"</string>
<string name="mmiFdnError" msgid="3975490266767565852">"কেৱল ফিক্সড ডায়েলিং নম্বৰৰ বাবে কার্য সীমাবদ্ধ কৰা হৈছে।"</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"আপুনি ৰ\'মিঙত থকাৰ সময়ত কল ফৰৱাৰ্ডিঙৰ ছেটিং সলনি কৰিব নোৱাৰি।"</string>
<string name="serviceEnabled" msgid="7549025003394765639">"সেৱা সক্ষম কৰা হ’ল।"</string>
diff --git a/core/res/res/values-az/strings.xml b/core/res/res/values-az/strings.xml
index bcee616c7696..292f51b382f8 100644
--- a/core/res/res/values-az/strings.xml
+++ b/core/res/res/values-az/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Səsli poçt"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"Bağlantı problemi və ya yalnış MM kodu."</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Funksiya dəstəklənmir."</string>
<string name="mmiFdnError" msgid="3975490266767565852">"Əməliyyat yalnız sabit nömrələrə yığımla məhdudlaşıb."</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Roaminqdə olarkən zəng yönləndirmə ayarlarını telefonunuzdan dəyişə bilməz."</string>
<string name="serviceEnabled" msgid="7549025003394765639">"Servis işə salındı."</string>
diff --git a/core/res/res/values-b+sr+Latn/strings.xml b/core/res/res/values-b+sr+Latn/strings.xml
index 4267c7d47cea..b1ccdf96797b 100644
--- a/core/res/res/values-b+sr+Latn/strings.xml
+++ b/core/res/res/values-b+sr+Latn/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Glasovna pošta"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"Problemi sa vezom ili nevažeći MMI kôd."</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Funkcija nije podržana."</string>
<string name="mmiFdnError" msgid="3975490266767565852">"Rad je ograničen samo na brojeve fiksnog biranja."</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Ne možete da promenite podešavanja preusmeravanja poziva sa telefona dok ste u romingu."</string>
<string name="serviceEnabled" msgid="7549025003394765639">"Usluga je omogućena."</string>
diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml
index e99c1cb22c23..4cd558d53220 100644
--- a/core/res/res/values-be/strings.xml
+++ b/core/res/res/values-be/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Галасавая пошта"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"Праблема падлучэння ці няправільны код MMI."</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Функцыя не падтрымліваецца."</string>
<string name="mmiFdnError" msgid="3975490266767565852">"Выкарыстанне абмежаванае толькі дазволенымі нумарамі."</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Немагчыма змяніць налады пераадрасацыі выклікаў з тэлефона, пакуль вы знаходзіцеся ў роўмінгу."</string>
<string name="serviceEnabled" msgid="7549025003394765639">"Служба была ўключана."</string>
diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml
index 5d1e8b5e0825..d39425be669c 100644
--- a/core/res/res/values-bg/strings.xml
+++ b/core/res/res/values-bg/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Гласова поща"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"Има проблем с връзката или MMI кодът е невалиден."</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Функцията не се поддържа."</string>
<string name="mmiFdnError" msgid="3975490266767565852">"Операцията е ограничена само до фиксираните номера за набиране."</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Докато сте в режим на роуминг, настройките за пренасочване на обажданията не могат да се променят от телефона ви."</string>
<string name="serviceEnabled" msgid="7549025003394765639">"Услугата бе активирана."</string>
diff --git a/core/res/res/values-bn/strings.xml b/core/res/res/values-bn/strings.xml
index 1ffe1507aad1..2836580dba95 100644
--- a/core/res/res/values-bn/strings.xml
+++ b/core/res/res/values-bn/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"ভয়েসমেল"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"সংযোগ সমস্যা বা অবৈধ MMI কোড৷"</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"ফিচার কাজ করে না।"</string>
<string name="mmiFdnError" msgid="3975490266767565852">"নির্দিষ্ট নম্বরে ডায়ালযোগ্য হিসেবে প্রক্রিয়াটি সীমিত করা হয়েছে৷"</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"আপনি রোমিংয়ে থাকাকালীন আপনার ফোন থেকে \'কল ফরওয়ার্ড করার সেটিংস\' পরিবর্তন করা যাবে না৷"</string>
<string name="serviceEnabled" msgid="7549025003394765639">"পরিষেবা সক্ষম করা ছিল৷"</string>
diff --git a/core/res/res/values-bs/strings.xml b/core/res/res/values-bs/strings.xml
index f1523927430c..0486932c7009 100644
--- a/core/res/res/values-bs/strings.xml
+++ b/core/res/res/values-bs/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Govorna pošta"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"Problem sa povezivanjem ili nevažeći MMI kôd."</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Funkcija nije podržana."</string>
<string name="mmiFdnError" msgid="3975490266767565852">"Operacija je ograničena samo na brojeve fiksnog biranja."</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Nije moguće promijeniti postavke prosljeđivanja poziva s vašeg telefona dok ste u romingu."</string>
<string name="serviceEnabled" msgid="7549025003394765639">"Usluga je omogućena."</string>
diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml
index 1f267acac3dc..8936e4fec665 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Bústia de veu"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"Problema de connexió o codi MMI no vàlid."</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"La funció no s\'admet."</string>
<string name="mmiFdnError" msgid="3975490266767565852">"L\'operació està restringida a números de marcatge fixos."</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"No es pot canviar la configuració de desviació de trucades del telèfon quan estàs en itinerància."</string>
<string name="serviceEnabled" msgid="7549025003394765639">"El servei s\'ha activat."</string>
@@ -395,54 +396,30 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Permet que l\'aplicació faci que parts de la seva memòria siguin persistents. Aquesta acció pot limitar la memòria disponible per a altres aplicacions i alentir el telèfon."</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"executar serveis en primer pla"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"Permet que l\'aplicació utilitzi serveis en primer pla."</string>
- <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
- <skip />
- <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
- <skip />
- <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
- <skip />
- <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
- <skip />
- <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
- <skip />
- <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
- <skip />
- <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
- <skip />
- <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
- <skip />
- <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
- <skip />
- <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
- <skip />
- <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
- <skip />
- <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
- <skip />
+ <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"executa serveis en primer pla amb el tipus \"camera\""</string>
+ <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"Permet que l\'aplicació utilitzi serveis en primer pla amb el tipus \"camera\""</string>
+ <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"executa serveis en primer pla amb el tipus \"connectedDevice\""</string>
+ <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"Permet que l\'aplicació utilitzi serveis en primer pla amb el tipus \"connectedDevice\""</string>
+ <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"executa serveis en primer pla amb el tipus \"dataSync\""</string>
+ <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"Permet que l\'aplicació utilitzi serveis en primer pla amb el tipus \"dataSync\""</string>
+ <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"executa serveis en primer pla amb el tipus \"location\""</string>
+ <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"Permet que l\'aplicació utilitzi serveis en primer pla amb el tipus \"location\""</string>
+ <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"executa serveis en primer pla amb el tipus \"mediaPlayback\""</string>
+ <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"Permet que l\'aplicació utilitzi serveis en primer pla amb el tipus \"mediaPlayback\""</string>
+ <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"executa serveis en primer pla amb el tipus \"mediaProjection\""</string>
+ <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"Permet que l\'aplicació utilitzi serveis en primer pla amb el tipus \"mediaProjection\""</string>
+ <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"executa serveis en primer pla amb el tipus \"microphone\""</string>
+ <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"Permet que l\'aplicació utilitzi serveis en primer pla amb el tipus \"microphone\""</string>
+ <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"executa serveis en primer pla amb el tipus \"phoneCall\""</string>
+ <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"Permet que l\'aplicació utilitzi serveis en primer pla amb el tipus \"phoneCall\""</string>
+ <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"executa serveis en primer pla amb el tipus \"health\""</string>
+ <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"Permet que l\'aplicació utilitzi serveis en primer pla amb el tipus \"health\""</string>
+ <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"executa serveis en primer pla amb el tipus \"remoteMessaging\""</string>
+ <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"Permet que l\'aplicació utilitzi serveis en primer pla amb el tipus \"remoteMessaging\""</string>
+ <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"executa serveis en primer pla amb el tipus \"systemExempted\""</string>
+ <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Permet que l\'aplicació utilitzi serveis en primer pla amb el tipus \"systemExempted\""</string>
+ <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"executa serveis en primer pla amb el tipus \"specialUse\""</string>
+ <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Permet que l\'aplicació utilitzi serveis en primer pla amb el tipus \"specialUse\""</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"mesura l\'espai d\'emmagatzematge d\'aplicacions"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"Permet que l\'aplicació recuperi les mides del codi, de les dades i de la memòria cau"</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"modificar la configuració del sistema"</string>
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index ff49c69e8b46..91daf786e8fd 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Hlasová schránka"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"Problém s připojením nebo neplatný kód MMI."</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Funkce není podporována."</string>
<string name="mmiFdnError" msgid="3975490266767565852">"Operace je omezena pouze na povolená telefonní čísla."</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Když je aktivní roaming, nastavení přesměrování hovorů z telefonu nelze změnit."</string>
<string name="serviceEnabled" msgid="7549025003394765639">"Služba byla zapnuta."</string>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index 9c069e7367c4..b1f5e2af3f7f 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Telefonsvarer"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"Forbindelsesproblemer eller ugyldigt MMI-nummer."</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Funktionen understøttes ikke."</string>
<string name="mmiFdnError" msgid="3975490266767565852">"Du kan kun foretage handlinger med dine numre til begrænset opkald."</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Det er ikke muligt at ændre indstillingerne for viderestilling af opkald fra din telefon, mens du bruger roaming."</string>
<string name="serviceEnabled" msgid="7549025003394765639">"Tjenesten blev aktiveret."</string>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index efc7e96d286c..9e89501f8c7b 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Mailbox"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"Verbindungsproblem oder ungültiger MMI-Code."</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Funktion nicht unterstützt."</string>
<string name="mmiFdnError" msgid="3975490266767565852">"Der Vorgang ist nur für deine zugelassenen Rufnummern möglich."</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Die Einstellungen für die Anrufweiterleitung von deinem Smartphone können während des Roamings nicht geändert werden."</string>
<string name="serviceEnabled" msgid="7549025003394765639">"Dienst wurde aktiviert."</string>
@@ -395,54 +396,30 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Ermöglicht der App, Teile der eigenen App dauerhaft im Speicher abzulegen. Dies kann dazu führen, dass anderen Apps weniger Arbeitsspeicher zur Verfügung steht und das Telefon langsamer wird."</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"Vordergrunddienst ausführen"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"Ermöglicht der App, die Vordergrunddienste zu verwenden."</string>
- <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
- <skip />
- <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
- <skip />
- <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
- <skip />
- <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
- <skip />
- <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
- <skip />
- <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
- <skip />
- <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
- <skip />
- <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
- <skip />
- <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
- <skip />
- <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
- <skip />
- <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
- <skip />
- <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
- <skip />
+ <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"Vordergrunddienste mit dem Typ „camera“ ausführen"</string>
+ <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"Ermöglicht der App, Vordergrunddienste mit dem Typ „camera“ zu verwenden"</string>
+ <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"Vordergrunddienste mit dem Typ „connectedDevice“ ausführen"</string>
+ <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"Ermöglicht der App, Vordergrunddienste mit dem Typ „connectedDevice“ zu verwenden"</string>
+ <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"Vordergrunddienste mit dem Typ „dataSync“ ausführen"</string>
+ <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"Ermöglicht der App, Vordergrunddienste mit dem Typ „dataSync“ zu verwenden"</string>
+ <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"Vordergrunddienste mit dem Typ „location“ ausführen"</string>
+ <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"Ermöglicht der App, Vordergrunddienste mit dem Typ „location“ zu verwenden"</string>
+ <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"Vordergrunddienste mit dem Typ „mediaPlayback“ ausführen"</string>
+ <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"Ermöglicht der App, Vordergrunddienste mit dem Typ „mediaPlayback“ zu verwenden"</string>
+ <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"Vordergrunddienste mit dem Typ „mediaProjection“ ausführen"</string>
+ <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"Ermöglicht der App, Vordergrunddienste mit dem Typ „mediaProjection“ zu verwenden"</string>
+ <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"Vordergrunddienste mit dem Typ „microphone“ ausführen"</string>
+ <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"Ermöglicht der App, Vordergrunddienste mit dem Typ „microphone“ zu verwenden"</string>
+ <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"Vordergrunddienste mit dem Typ „phoneCall“ ausführen"</string>
+ <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"Ermöglicht der App, Vordergrunddienste mit dem Typ „phoneCall“ zu verwenden"</string>
+ <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"Vordergrunddienste mit dem Typ „health“ ausführen"</string>
+ <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"Ermöglicht der App, Vordergrunddienste mit dem Typ „health“ zu verwenden"</string>
+ <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"Vordergrunddienste mit dem Typ „remoteMessaging“ ausführen"</string>
+ <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"Ermöglicht der App, Vordergrunddienste mit dem Typ „remoteMessaging“ zu verwenden"</string>
+ <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"Vordergrunddienste mit dem Typ „systemExempted“ ausführen"</string>
+ <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Ermöglicht der App, Vordergrunddienste mit dem Typ „systemExempted“ zu verwenden"</string>
+ <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"Vordergrunddienste mit dem Typ „specialUse“ ausführen"</string>
+ <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Ermöglicht der App, Vordergrunddienste mit dem Typ „specialUse“ zu verwenden"</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"Speicherplatz der App ermitteln"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"Ermöglicht der App, ihre Code-, Daten- und Cache-Größe abzurufen"</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"Systemeinstellungen ändern"</string>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index df8a6bb8b62f..8f555895be95 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Αυτ/τος τηλεφωνητής"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"Πρόβλημα σύνδεσης ή μη έγκυρος κώδικας MMI."</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Η λειτουργία δεν υποστηρίζεται."</string>
<string name="mmiFdnError" msgid="3975490266767565852">"Η λειτουργία περιορίζεται μόνο σε προκαθορισμένους αριθμούς κλήσης."</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Δεν είναι δυνατή η αλλαγή των ρυθμίσεων προώθησης κλήσεων από το τηλέφωνό σας κατά τη διάρκεια της περιαγωγής."</string>
<string name="serviceEnabled" msgid="7549025003394765639">"Η υπηρεσία ενεργοποιήθηκε."</string>
diff --git a/core/res/res/values-en-rAU/strings.xml b/core/res/res/values-en-rAU/strings.xml
index 114c3756aea5..003b3f0dae63 100644
--- a/core/res/res/values-en-rAU/strings.xml
+++ b/core/res/res/values-en-rAU/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Voicemail"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"Connection problem or invalid MMI code."</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Feature not supported."</string>
<string name="mmiFdnError" msgid="3975490266767565852">"Operation is restricted to fixed dialling numbers only."</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Cannot change call forwarding settings from your phone while you are roaming."</string>
<string name="serviceEnabled" msgid="7549025003394765639">"Service was enabled."</string>
diff --git a/core/res/res/values-en-rCA/strings.xml b/core/res/res/values-en-rCA/strings.xml
index 9c9f0668bf48..e1cfd83ad089 100644
--- a/core/res/res/values-en-rCA/strings.xml
+++ b/core/res/res/values-en-rCA/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Voicemail"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"Connection problem or invalid MMI code."</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Feature not supported."</string>
<string name="mmiFdnError" msgid="3975490266767565852">"Operation is restricted to fixed dialing numbers only."</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Can not change call forwarding settings from your phone while you are roaming."</string>
<string name="serviceEnabled" msgid="7549025003394765639">"Service was enabled."</string>
diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml
index d11f7338e439..4c0a7aa42594 100644
--- a/core/res/res/values-en-rGB/strings.xml
+++ b/core/res/res/values-en-rGB/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Voicemail"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"Connection problem or invalid MMI code."</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Feature not supported."</string>
<string name="mmiFdnError" msgid="3975490266767565852">"Operation is restricted to fixed dialling numbers only."</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Cannot change call forwarding settings from your phone while you are roaming."</string>
<string name="serviceEnabled" msgid="7549025003394765639">"Service was enabled."</string>
diff --git a/core/res/res/values-en-rIN/strings.xml b/core/res/res/values-en-rIN/strings.xml
index 8dd085a66349..7e3ce2d6e2e3 100644
--- a/core/res/res/values-en-rIN/strings.xml
+++ b/core/res/res/values-en-rIN/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Voicemail"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"Connection problem or invalid MMI code."</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Feature not supported."</string>
<string name="mmiFdnError" msgid="3975490266767565852">"Operation is restricted to fixed dialling numbers only."</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Cannot change call forwarding settings from your phone while you are roaming."</string>
<string name="serviceEnabled" msgid="7549025003394765639">"Service was enabled."</string>
diff --git a/core/res/res/values-en-rXC/strings.xml b/core/res/res/values-en-rXC/strings.xml
index 2197501a7eb4..9cc06d18aec4 100644
--- a/core/res/res/values-en-rXC/strings.xml
+++ b/core/res/res/values-en-rXC/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‏‎‎‏‏‎‎‏‏‏‎‎‎‏‏‏‏‏‏‎‎‏‏‎‏‏‎‎‏‏‏‎‏‎‏‎‏‏‎‎‏‎‏‏‏‏‏‏‎‏‏‏‎‎‏‎‏‎‎Voicemail‎‏‎‎‏‎"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‏‏‏‎‏‏‎‏‏‎‎‎‎‏‎‎‏‎‏‏‎‏‏‎‏‏‎‎‎‎‏‎‎‏‎‏‏‎‏‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‎‎‎‎‎MSISDN1‎‏‎‎‏‎"</string>
<string name="mmiError" msgid="2862759606579822246">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‏‏‏‎‏‏‏‎‏‎‏‎‎‏‎‎‎‎‏‎‏‏‎‎‎‏‏‏‎‏‎‎‏‏‏‎‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‎‏‎‎‏‏‎‎Connection problem or invalid MMI code.‎‏‎‎‏‎"</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‎‏‎‏‏‎‏‎‎‏‏‏‏‏‏‎‎‏‏‏‎‎‎‎‎‏‎‎‏‏‎‎‏‎‎‎‎‎‏‎‏‏‏‏‎‏‎‎‎‏‎‏‎‏‎‎‏‏‎Feature not supported.‎‏‎‎‏‎"</string>
<string name="mmiFdnError" msgid="3975490266767565852">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‏‏‎‎‏‎‏‎‏‏‏‏‎‎‎‏‏‏‎‏‎‏‎‏‎‏‏‎‏‎‎‏‏‎‏‎‏‏‎‏‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‎‎‎Operation is restricted to fixed dialing numbers only.‎‏‎‎‏‎"</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‎‎‏‎‏‏‎‏‏‎‎‎‎‏‎‏‎‏‏‎‎‏‎‎‎‏‎‏‏‎‏‏‎‏‎‎‏‎‎‏‏‎‏‎‎‎‏‎‏‏‎‎‏‎‎‎‏‎‎Can not change call forwarding settings from your phone while you are roaming.‎‏‎‎‏‎"</string>
<string name="serviceEnabled" msgid="7549025003394765639">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‎‎‏‏‎‎‎‎‏‏‏‎‎‎‎‏‏‎‎‎‏‏‏‏‏‎‎‏‏‏‏‏‏‏‏‏‏‎‎‎‏‏‎‎‎‏‎‎‏‏‎‏‎‎‎‏‏‏‎Service was enabled.‎‏‎‎‏‎"</string>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index 0d72ec562096..69ce57ef85c9 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Buzón de voz"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"Problema de conexión o código incorrecto de MMI."</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Función no compatible."</string>
<string name="mmiFdnError" msgid="3975490266767565852">"La operación está limitada a números de marcación fija."</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"No se puede cambiar la configuración de desvío de llamadas de tu teléfono mientras usas el servicio de roaming."</string>
<string name="serviceEnabled" msgid="7549025003394765639">"Se ha activado el servicio."</string>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index 76495a8ac8d3..91c12c92f11c 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Buzón de voz"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"Se ha producido un problema de conexión o el código MMI no es válido."</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Función no disponible."</string>
<string name="mmiFdnError" msgid="3975490266767565852">"La operación solo es válida para números de marcación fija."</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"No se puede cambiar la configuración de desvío de llamadas desde tu teléfono mientras estás en roaming."</string>
<string name="serviceEnabled" msgid="7549025003394765639">"El servicio se ha habilitado."</string>
@@ -88,7 +89,7 @@
<string name="notification_channel_network_alert" msgid="4788053066033851841">"Alertas"</string>
<string name="notification_channel_call_forward" msgid="8230490317314272406">"Desvío de llamadas"</string>
<string name="notification_channel_emergency_callback" msgid="54074839059123159">"Modo de devolución de llamada de emergencia"</string>
- <string name="notification_channel_mobile_data_status" msgid="1941911162076442474">"Estado de los datos móviles"</string>
+ <string name="notification_channel_mobile_data_status" msgid="1941911162076442474">"Estado de datos móviles"</string>
<string name="notification_channel_sms" msgid="1243384981025535724">"Mensajes SMS"</string>
<string name="notification_channel_voice_mail" msgid="8457433203106654172">"Mensajes de voz"</string>
<string name="notification_channel_wfc" msgid="9048240466765169038">"Llamada por Wi-Fi"</string>
@@ -396,54 +397,30 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Permite que la aplicación haga que algunas de sus partes se mantengan en la memoria. Esto puede limitar la cantidad de memoria disponible para otras aplicaciones y ralentizar el teléfono."</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"ejecutar servicio en primer plano"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"Permite que la aplicación use servicios en primer plano."</string>
- <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
- <skip />
- <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
- <skip />
- <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
- <skip />
- <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
- <skip />
- <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
- <skip />
- <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
- <skip />
- <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
- <skip />
- <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
- <skip />
- <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
- <skip />
- <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
- <skip />
- <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
- <skip />
- <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
- <skip />
+ <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"ejecutar un servicio en primer plano con el tipo \"camera\""</string>
+ <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"Permite que la aplicación use servicios en primer plano con el tipo \"camera\""</string>
+ <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"ejecutar un servicio en primer plano con el tipo \"connectedDevice\""</string>
+ <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"Permite que la aplicación use servicios en primer plano con el tipo \"connectedDevice\""</string>
+ <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"ejecutar un servicio en primer plano con el tipo \"dataSync\""</string>
+ <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"Permite que la aplicación use servicios en primer plano con el tipo \"dataSync\""</string>
+ <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"ejecutar un servicio en primer plano con el tipo \"location\""</string>
+ <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"Permite que la aplicación use servicios en primer plano con el tipo \"location\""</string>
+ <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"ejecutar un servicio en primer plano con el tipo \"mediaPlayback\""</string>
+ <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"Permite que la aplicación use servicios en primer plano con el tipo \"mediaPlayback\""</string>
+ <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"ejecutar un servicio en primer plano con el tipo \"mediaProjection\""</string>
+ <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"Permite que la aplicación use servicios en primer plano con el tipo \"mediaProjection\""</string>
+ <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"ejecutar un servicio en primer plano con el tipo \"microphone\""</string>
+ <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"Permite que la aplicación use servicios en primer plano con el tipo \"microphone\""</string>
+ <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"ejecutar un servicio en primer plano con el tipo \"phoneCall\""</string>
+ <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"Permite que la aplicación use servicios en primer plano con el tipo \"phoneCall\""</string>
+ <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"ejecutar un servicio en primer plano con el tipo \"health\""</string>
+ <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"Permite que la aplicación use servicios en primer plano con el tipo \"health\""</string>
+ <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"ejecutar un servicio en primer plano con el tipo \"remoteMessaging\""</string>
+ <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"Permite que la aplicación use servicios en primer plano con el tipo \"remoteMessaging\""</string>
+ <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"ejecutar un servicio en primer plano con el tipo \"systemExempted\""</string>
+ <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Permite que la aplicación use servicios en primer plano con el tipo \"systemExempted\""</string>
+ <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"ejecutar un servicio en primer plano con el tipo \"specialUse\""</string>
+ <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Permite que la aplicación use servicios en primer plano con el tipo \"specialUse\""</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"medir el espacio de almacenamiento de la aplicación"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"Permite que la aplicación recupere su código, sus datos y los tamaños de caché."</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"modificar los ajustes del sistema"</string>
diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml
index c16f7fce6431..42a383c2f324 100644
--- a/core/res/res/values-et/strings.xml
+++ b/core/res/res/values-et/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Kõnepost"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"Ühendusprobleem või kehtetu MMI-kood."</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Funktsiooni ei toetata."</string>
<string name="mmiFdnError" msgid="3975490266767565852">"Toiming on ainult fikseeritud valimisnumbritele."</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Kõne suunamise seadeid ei saa rändluse ajal teie telefonis muuta."</string>
<string name="serviceEnabled" msgid="7549025003394765639">"Teenus on lubatud."</string>
diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml
index 2c29a807aec2..878b57e3bbd1 100644
--- a/core/res/res/values-eu/strings.xml
+++ b/core/res/res/values-eu/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Erantzungailua"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"Konexio-arazoren bat gertatu da edo MMI kodea baliogabea da."</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Ez da onartzen eginbidea."</string>
<string name="mmiFdnError" msgid="3975490266767565852">"Eragiketa markatze finkoko zenbakietara murriztua dago."</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Ezin dira aldatu deiak desbideratzeko ezarpenak telefonoa ibiltaritzan dagoenean."</string>
<string name="serviceEnabled" msgid="7549025003394765639">"Zerbitzua gaitu da."</string>
diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml
index 4ee442c534a4..a65910b6247d 100644
--- a/core/res/res/values-fa/strings.xml
+++ b/core/res/res/values-fa/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"پست صوتی"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"‏مشکل در اتصال یا کد MMI نامعتبر."</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"از این ویژگی پشتیبانی نمی‌شود."</string>
<string name="mmiFdnError" msgid="3975490266767565852">"عملکرد فقط به شماره‌های شماره‌گیری ثابت محدود است."</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"وقتی درحال فراگردی هستید، نمی‌توانید تنظیمات هدایت تماس را از تلفنتان تغییر دهید."</string>
<string name="serviceEnabled" msgid="7549025003394765639">"سرویس فعال شد."</string>
diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml
index 158290df7d00..02ae8931d24f 100644
--- a/core/res/res/values-fi/strings.xml
+++ b/core/res/res/values-fi/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Vastaaja"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"Yhteysongelma tai virheellinen MMI-koodi."</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Ominaisuutta ei tueta."</string>
<string name="mmiFdnError" msgid="3975490266767565852">"Voit suorittaa toiminnon vain sallitut puhelut -numeroihin."</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Soitonsiirtoasetuksia ei voi muuttaa puhelimella roaming-tilassa."</string>
<string name="serviceEnabled" msgid="7549025003394765639">"Palvelu otettiin käyttöön."</string>
diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml
index 5f2d3dc2bdb5..264cf671b61f 100644
--- a/core/res/res/values-fr-rCA/strings.xml
+++ b/core/res/res/values-fr-rCA/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Messagerie vocale"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"Problème de connexion ou code IHM incorrect"</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Fonctionnalité non prise en charge."</string>
<string name="mmiFdnError" msgid="3975490266767565852">"Opération réservée aux numéros autorisés"</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Impossible de modifier les paramètres de transfert d\'appel sur votre téléphone lorsque vous êtes en itinérance."</string>
<string name="serviceEnabled" msgid="7549025003394765639">"Le service a été activé."</string>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index 563fa58396e0..0e4bb4c75fce 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Messagerie vocale"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"Problème de connexion ou code IHM non valide."</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Fonctionnalité non disponible."</string>
<string name="mmiFdnError" msgid="3975490266767565852">"Opération réservée aux numéros autorisés"</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Impossible de modifier les paramètres de transfert d\'appel depuis votre téléphone lorsque vous êtes en itinérance."</string>
<string name="serviceEnabled" msgid="7549025003394765639">"Le service a été activé."</string>
diff --git a/core/res/res/values-gl/strings.xml b/core/res/res/values-gl/strings.xml
index a931219daf50..31da9fae668f 100644
--- a/core/res/res/values-gl/strings.xml
+++ b/core/res/res/values-gl/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Correo de voz"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"Problema de conexión ou código MMI non válido."</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Función non compatible."</string>
<string name="mmiFdnError" msgid="3975490266767565852">"A operación está restrinxida a números de marcación fixa."</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Non se pode cambiar a configuración do desvío de chamadas desde o teléfono mentres estás en itinerancia."</string>
<string name="serviceEnabled" msgid="7549025003394765639">"Activouse o servizo."</string>
@@ -1978,13 +1979,13 @@
<string name="app_streaming_blocked_title_for_settings_dialog" product="tv" msgid="196994247017450357">"A configuración de Android TV non está dispoñible"</string>
<string name="app_streaming_blocked_title_for_settings_dialog" product="tablet" msgid="8222710146267948647">"A configuración da tableta non está dispoñible"</string>
<string name="app_streaming_blocked_title_for_settings_dialog" product="default" msgid="6895719984375299791">"A configuración do teléfono non está dispoñible"</string>
- <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"Nestes momentos, non podes acceder a este contido desde o teu dispositivo (<xliff:g id="DEVICE">%1$s</xliff:g>). Proba a facelo desde o dispositivo con Android TV."</string>
+ <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"Nestes momentos, non podes acceder a este contido desde o teu dispositivo (<xliff:g id="DEVICE">%1$s</xliff:g>). Proba a facelo desde o dispositivo Android TV."</string>
<string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"Nestes momentos, non podes acceder a este contido desde o teu dispositivo (<xliff:g id="DEVICE">%1$s</xliff:g>). Proba a facelo desde a tableta."</string>
<string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"Nestes momentos, non podes acceder a este contido desde o teu dispositivo (<xliff:g id="DEVICE">%1$s</xliff:g>). Proba a facelo desde o teléfono."</string>
- <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"Esta aplicación solicita seguranza adicional. Proba a facelo desde o dispositivo con Android TV."</string>
+ <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"Esta aplicación solicita seguranza adicional. Proba a facelo desde o dispositivo Android TV."</string>
<string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"Esta aplicación solicita seguranza adicional. Proba a facelo desde a tableta."</string>
<string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"Esta aplicación solicita seguranza adicional. Proba a facelo desde o teléfono."</string>
- <string name="app_streaming_blocked_message_for_settings_dialog" product="tv" msgid="820334666354451145">"Non se puido acceder a este contido desde o teu dispositivo (<xliff:g id="DEVICE">%1$s</xliff:g>). Proba a facelo desde o dispositivo con Android TV."</string>
+ <string name="app_streaming_blocked_message_for_settings_dialog" product="tv" msgid="820334666354451145">"Non se puido acceder a este contido desde o teu dispositivo (<xliff:g id="DEVICE">%1$s</xliff:g>). Proba a facelo desde o dispositivo Android TV."</string>
<string name="app_streaming_blocked_message_for_settings_dialog" product="tablet" msgid="3286849551133045896">"Non se puido acceder a este contido desde o teu dispositivo (<xliff:g id="DEVICE">%1$s</xliff:g>). Proba a facelo desde a tableta."</string>
<string name="app_streaming_blocked_message_for_settings_dialog" product="default" msgid="6264287556598916295">"Non se puido acceder a este contido desde o teu dispositivo (<xliff:g id="DEVICE">%1$s</xliff:g>). Proba a facelo desde o teléfono."</string>
<string name="deprecated_target_sdk_message" msgid="5246906284426844596">"Esta aplicación deseñouse para unha versión anterior de Android. Quizais non funcione correctamente e non inclúa as últimas medidas de protección de privacidade e seguranza. Comproba se hai actualizacións ou ponte en contacto co programador da aplicación."</string>
diff --git a/core/res/res/values-gu/strings.xml b/core/res/res/values-gu/strings.xml
index 418eb48bcbe7..8351fbc89230 100644
--- a/core/res/res/values-gu/strings.xml
+++ b/core/res/res/values-gu/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"વૉઇસમેઇલ"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"કનેક્શન સમસ્યા અથવા અમાન્ય MMI કોડ."</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"સુવિધાને સપોર્ટ આપવામાં આવતો નથી."</string>
<string name="mmiFdnError" msgid="3975490266767565852">"ઑપરેશન ફક્ત સ્થિર ડાયલિંગ નંબર્સ પર પ્રતિબંધિત છે."</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"તમે રોમિંગમાં હો તે વખતે તમારા ફોન પરથી કૉલ ફૉરવર્ડિગ સેટિંગ બદલી શકતાં નથી."</string>
<string name="serviceEnabled" msgid="7549025003394765639">"સેવા સક્ષમ હતી."</string>
diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml
index 2406650a1ec5..5bc7273b50b8 100644
--- a/core/res/res/values-hi/strings.xml
+++ b/core/res/res/values-hi/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"वॉइसमेल"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"कनेक्‍शन समस्‍या या अमान्‍य MMI कोड."</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"यह सुविधा, इस नेटवर्क पर काम नहीं करती है."</string>
<string name="mmiFdnError" msgid="3975490266767565852">"कार्रवाई केवल फ़िक्‍स्‍ड डायलिंग नंबर के लिए प्रतिबंधित है."</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"आपके रोमिंग में होने पर, आपके फ़ोन से कॉल को दूसरे नंबर पर भेजने की सेटिंग नहीं बदली जा सकती."</string>
<string name="serviceEnabled" msgid="7549025003394765639">"सेवा अक्षम थी."</string>
diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml
index 407eb4e6090d..6d6d5fe7ace4 100644
--- a/core/res/res/values-hr/strings.xml
+++ b/core/res/res/values-hr/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Govorna pošta"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"Problem s vezom ili nevažeći MMI kôd."</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Značajka nije podržana."</string>
<string name="mmiFdnError" msgid="3975490266767565852">"Operacija je ograničena samo na brojeve s fiksnim biranjem."</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Nije moguće promijeniti postavke preusmjeravanja poziva na telefonu dok ste u roamingu."</string>
<string name="serviceEnabled" msgid="7549025003394765639">"Usluga je omogućena."</string>
diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml
index 34ee8a3106ad..05953f0b98df 100644
--- a/core/res/res/values-hu/strings.xml
+++ b/core/res/res/values-hu/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Hangposta"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"Kapcsolódási probléma vagy érvénytelen MMI-kód."</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"A funkció nem támogatott."</string>
<string name="mmiFdnError" msgid="3975490266767565852">"A művelet fix hívószámokra van korlátozva."</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"A hívásátirányítási beállításokat roaming közben telefonról nem lehet módosítani."</string>
<string name="serviceEnabled" msgid="7549025003394765639">"A szolgáltatás engedélyezésre került."</string>
diff --git a/core/res/res/values-hy/strings.xml b/core/res/res/values-hy/strings.xml
index 91646a41e28d..7f8898eee3b5 100644
--- a/core/res/res/values-hy/strings.xml
+++ b/core/res/res/values-hy/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Ձայնային փոստ"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"Միացման խնդիր կամ անվավեր MMI ծածակագիր:"</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Գործառույթը չի աջակցվում։"</string>
<string name="mmiFdnError" msgid="3975490266767565852">"Գործողությունը սահմանափակված է միայն ամրակայված հեռախոսահամարների համար:"</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Ռոումինգում չեք կարող փոխել զանգի վերահասցեավորման կարգավորումները ձեր հեռախոսից։"</string>
<string name="serviceEnabled" msgid="7549025003394765639">"Ծառայությունը միացված է:"</string>
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index df7715bdce4c..79ee0e8d47ff 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Pesan suara"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"Masalah sambungan atau kode MMI tidak valid."</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Fitur tidak didukung."</string>
<string name="mmiFdnError" msgid="3975490266767565852">"Operasi dibatasi untuk nomor panggilan tetap saja."</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Tidak dapat mengubah setelan penerusan panggilan dari ponsel saat roaming"</string>
<string name="serviceEnabled" msgid="7549025003394765639">"Layanan telah diaktifkan."</string>
diff --git a/core/res/res/values-is/strings.xml b/core/res/res/values-is/strings.xml
index a97ac9849902..5343d237ce5c 100644
--- a/core/res/res/values-is/strings.xml
+++ b/core/res/res/values-is/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Talhólf"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"Vandamál með tengingu eða ógild MMI-kóðaskipun."</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Eiginleiki ekki studdur."</string>
<string name="mmiFdnError" msgid="3975490266767565852">"Aðgerð takmarkast við fast númeraval."</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Ekki er hægt að breyta stillingum fyrir framsendingu símtala úr símanum á meðan þú ert í reiki."</string>
<string name="serviceEnabled" msgid="7549025003394765639">"Þjónustan var virkjuð."</string>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index e27538a05750..64c4fd543e53 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Segreteria"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"Problema di connessione o codice MMI non valido."</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Funzionalità non supportata."</string>
<string name="mmiFdnError" msgid="3975490266767565852">"Operazione limitata solo ai numeri di selezione fissa."</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Impossibile modificare le impostazioni di deviazione chiamate dal telefono durante il roaming."</string>
<string name="serviceEnabled" msgid="7549025003394765639">"Il servizio è stato attivato."</string>
diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml
index a074860db006..a6c038353982 100644
--- a/core/res/res/values-iw/strings.xml
+++ b/core/res/res/values-iw/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"דואר קולי"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"‏בעיה בחיבור או קוד MMI לא חוקי."</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"התכונה לא נתמכת."</string>
<string name="mmiFdnError" msgid="3975490266767565852">"הפעולה מוגבלת למספרי חיוג קבועים בלבד."</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"לא ניתן לשנות את הגדרות העברת השיחות מהטלפון במצב נדידה."</string>
<string name="serviceEnabled" msgid="7549025003394765639">"השירות הופעל."</string>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index 140ba8857874..9bcde385bdea 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"留守番電話"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"接続に問題があるか、MMIコードが正しくありません。"</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"サポートされていません。"</string>
<string name="mmiFdnError" msgid="3975490266767565852">"発信番号制限で指定された番号に対してのみ操作できます。"</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"ローミング中はスマートフォンから着信転送設定の変更はできません。"</string>
<string name="serviceEnabled" msgid="7549025003394765639">"サービスが有効になりました。"</string>
diff --git a/core/res/res/values-ka/strings.xml b/core/res/res/values-ka/strings.xml
index a5371d215b3b..92a37ce47d53 100644
--- a/core/res/res/values-ka/strings.xml
+++ b/core/res/res/values-ka/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"ხმოვანი ფოსტა"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"კავშირის პრობლემა ან არასწორი MMI კოდი."</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"ფუნქცია მხარდაუჭერელია."</string>
<string name="mmiFdnError" msgid="3975490266767565852">"ოპერაცია შეზღუდულია მხოლოდ დაშვებულ ნომრებზე."</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"ზარის გადამისამართების პარამეტრების თქვენი ტელეფონიდან შეცვლა როუმინგისას ვერ მოხერხდება."</string>
<string name="serviceEnabled" msgid="7549025003394765639">"სერვისი ჩართულია."</string>
diff --git a/core/res/res/values-kk/strings.xml b/core/res/res/values-kk/strings.xml
index 47afaa301602..04f0445c1e26 100644
--- a/core/res/res/values-kk/strings.xml
+++ b/core/res/res/values-kk/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Дауыстық пошта"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"Байланыс мәселесі немесе MMИ коды жарамсыз."</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Функция қолданылмайды."</string>
<string name="mmiFdnError" msgid="3975490266767565852">"Әрекет анықталған сандарды теруге шектелген."</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Роуминг кезінде телефоннан қоңырауды басқа нөмірге бағыттау параметрлері өзгертілмейді."</string>
<string name="serviceEnabled" msgid="7549025003394765639">"Қызмет қосылған."</string>
@@ -395,54 +396,30 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Қолданбаға өзінің бөліктерін жадта бекіндіру мүмкіндігін береді. Бұл басқа қолданбалардың жадқа қол жетімділігін шектеп, телефонды баяулатуы мүмкін."</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"басымдылығы жоғары қызметті іске қосу"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"Қолданбаға басымдылығы жоғары қызметтерді пайдалануға рұқсат береді."</string>
- <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
- <skip />
- <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
- <skip />
- <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
- <skip />
- <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
- <skip />
- <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
- <skip />
- <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
- <skip />
- <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
- <skip />
- <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
- <skip />
- <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
- <skip />
- <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
- <skip />
- <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
- <skip />
- <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
- <skip />
+ <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"\"camera\" түріне жататын экрандық режимдегі қызметті іске қосу"</string>
+ <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"Қолданбаға \"camera\" түріне жататын экрандық режимдегі қызметтерді пайдалануға рұқсат беріледі."</string>
+ <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"\"health\" түріне жататын экрандық режимдегі қызметті іске қосу"</string>
+ <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"Қолданбаға \"connectedDevice\" түріне жататын экрандық режимдегі қызметтерді пайдалануға рұқсат беріледі."</string>
+ <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"\"dataSync\" түріне жататын экрандық режимдегі қызметті іске қосу"</string>
+ <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"Қолданбаға \"dataSync\" түріне жататын экрандық режимдегі қызметтерді пайдалануға рұқсат беріледі."</string>
+ <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"\"location\" түріне жататын экрандық режимдегі қызметті іске қосу"</string>
+ <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"Қолданбаға \"location\" түріне жататын экрандық режимдегі қызметтерді пайдалануға рұқсат беріледі."</string>
+ <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"\"mediaPlayback\" түріне жататын экрандық режимдегі қызметті іске қосу"</string>
+ <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"Қолданбаға \"mediaPlayback\" түріне жататын экрандық режимдегі қызметтерді пайдалануға рұқсат беріледі."</string>
+ <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"\"mediaProjection\" түріне жататын экрандық режимдегі қызметті іске қосу"</string>
+ <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"Қолданбаға \"mediaProjection\" түріне жататын экрандық режимдегі қызметтерді пайдалануға рұқсат беріледі."</string>
+ <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"\"microphone\" түріне жататын экрандық режимдегі қызметті іске қосу"</string>
+ <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"Қолданбаға \"microphone\" түріне жататын экрандық режимдегі қызметтерді пайдалануға рұқсат беріледі."</string>
+ <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"\"phoneCall\" түріне жататын экрандық режимдегі қызметті іске қосу"</string>
+ <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"Қолданбаға \"phoneCall\" түріне жататын экрандық режимдегі қызметтерді пайдалануға рұқсат беріледі."</string>
+ <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"\"health\" түріне жататын экрандық режимдегі қызметті іске қосу"</string>
+ <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"Қолданбаға \"health\" түріне жататын экрандық режимдегі қызметтерді пайдалануға рұқсат беріледі."</string>
+ <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"\"remoteMessaging\" түріне жататын экрандық режимдегі қызметті іске қосу"</string>
+ <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"Қолданбаға \"remoteMessaging\" түріне жататын экрандық режимдегі қызметтерді пайдалануға рұқсат беріледі."</string>
+ <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"\"systemExempted\" түріне жататын экрандық режимдегі қызметті іске қосу"</string>
+ <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Қолданбаға \"systemExempted\" түріне жататын экрандық режимдегі қызметтерді пайдалануға рұқсат беріледі."</string>
+ <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"\"specialUse\" түріне жататын экрандық режимдегі қызметті іске қосу"</string>
+ <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Қолданбаға \"specialUse\" түріне жататын экрандық режимдегі қызметтерді пайдалануға рұқсат беріледі."</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"қолданба жадындағы бос орынды өлшеу"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"Қолданбаға оның кодын, деректерін және кэш өлшемдерін шығарып алуға рұқсат береді"</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"жүйе параметрлерін өзгерту"</string>
diff --git a/core/res/res/values-km/strings.xml b/core/res/res/values-km/strings.xml
index 3f0ae1fa76cf..147771313f4a 100644
--- a/core/res/res/values-km/strings.xml
+++ b/core/res/res/values-km/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"សារ​ជា​សំឡេង"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"បញ្ហា​ក្នុង​ការ​តភ្ជាប់​ ឬ​កូដ MMI មិន​ត្រឹមត្រូវ។"</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"មិនអាចប្រើមុខងារនេះបានទេ។"</string>
<string name="mmiFdnError" msgid="3975490266767565852">"ប្រតិបត្តិការ​ត្រូវ​បាន​ដាក់​កម្រិត​​​ចំពោះ​លេខ​ហៅ​ថេរ​តែ​ប៉ុណ្ណោះ។"</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"មិន​អាច​ប្តូរ​ការ​កំណត់​នៃ​ការ​បញ្ជូន​ការ​ហៅ​បន្ត​ពី​ទូរសព្ទ​របស់​អ្នក​បាន​ទេ​ ខណៈ​ពេល​ដែល​អ្នក​កំពុង​ប្រើ​សេវា​រ៉ូមីង។"</string>
<string name="serviceEnabled" msgid="7549025003394765639">"បាន​បើក​សេវាកម្ម។"</string>
diff --git a/core/res/res/values-kn/strings.xml b/core/res/res/values-kn/strings.xml
index 2a550defcb0d..b5535c927b72 100644
--- a/core/res/res/values-kn/strings.xml
+++ b/core/res/res/values-kn/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"ಧ್ವನಿಮೇಲ್"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"ಸಂಪರ್ಕ ಸಮಸ್ಯೆ ಇಲ್ಲವೇ ಅಮಾನ್ಯ MMI ಕೋಡ್."</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"ಫೀಚರ್ ಬಂಬಲಿಸುತ್ತಿಲ್ಲ."</string>
<string name="mmiFdnError" msgid="3975490266767565852">"ಕಾರ್ಯಾಚರಣೆಯನ್ನು ಸ್ಥಿರ ದೂರವಾಣಿ ಸಂಖ್ಯೆಗಳಿಗೆ ಮಾತ್ರ ನಿರ್ಬಂಧಿಸಲಾಗಿದೆ."</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"ನೀವು ರೋಮಿಂಗ್‌ನಲ್ಲಿರುವಾಗ ನಿಮ್ಮ ಫೋನ್‌ನಿಂದ ಕರೆ ಫಾರ್ವರ್ಡ್ ಮಾಡುವಿಕೆಯ ಸೆಟ್ಟಿಂಗ್‌ಗಳನ್ನು ಬದಲಾಯಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ."</string>
<string name="serviceEnabled" msgid="7549025003394765639">"ಸೇವೆಯನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ."</string>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index f55dfab9f3f7..aa6feae9fdfc 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"음성사서함"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"연결에 문제가 있거나 MMI 코드가 잘못되었습니다."</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"기능이 지원되지 않습니다."</string>
<string name="mmiFdnError" msgid="3975490266767565852">"발신 허용 번호에서만 수행할 수 있는 작업입니다."</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"로밍 중에는 착신 전환 설정을 변경할 수 없습니다."</string>
<string name="serviceEnabled" msgid="7549025003394765639">"서비스를 사용하도록 설정했습니다."</string>
diff --git a/core/res/res/values-ky/strings.xml b/core/res/res/values-ky/strings.xml
index 26e5c1e4bf86..8201c50222bd 100644
--- a/core/res/res/values-ky/strings.xml
+++ b/core/res/res/values-ky/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Үн почтасы"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"Туташууда көйгөй чыкты же MMI коду жараксыз."</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Функция колдоого алынбайт."</string>
<string name="mmiFdnError" msgid="3975490266767565852">"Иш-аракет туруктуу терүү номерлери менен гана чектелет."</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Роуминг учурунда чалууну башка номерге багыттоонун жөндөөлөрүн телефонуңуздан өзгөртүү мүмкүн эмес."</string>
<string name="serviceEnabled" msgid="7549025003394765639">"Кызмат иштетилди."</string>
diff --git a/core/res/res/values-lo/strings.xml b/core/res/res/values-lo/strings.xml
index 4b8e82b06fcf..d7b77bfc6af7 100644
--- a/core/res/res/values-lo/strings.xml
+++ b/core/res/res/values-lo/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"ຂໍ້ຄວາມສຽງ"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"ມີບັນຫາໃນການເຊື່ອມຕໍ່ ຫຼືລະຫັດ MMI ບໍ່ຖືກຕ້ອງ."</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"ບໍ່ຮອງຮັບຄຸນສົມບັດ."</string>
<string name="mmiFdnError" msgid="3975490266767565852">"ການດຳເນີນການຖືກຈຳກັດເປັນ ຈຳກັດໝາຍເລກໂທອອກເທົ່ານັ້ນ."</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Can not change call forwarding settings from your phone while you are roaming."</string>
<string name="serviceEnabled" msgid="7549025003394765639">"ບໍລິການຖືກເປີດໄວ້ແລ້ວ."</string>
diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml
index 341612c1b724..eb749709caca 100644
--- a/core/res/res/values-lt/strings.xml
+++ b/core/res/res/values-lt/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Balso paštas"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"Ryšio problema arba neteisingas MMI kodas."</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Funkcija nepalaikoma."</string>
<string name="mmiFdnError" msgid="3975490266767565852">"Operacija ribojama tik naudojant fiksuoto rinkimo numerius."</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Negalima pakeisti telefono skambučio peradresavimo nustatymų, kai naudojate tarptinklinį ryšį."</string>
<string name="serviceEnabled" msgid="7549025003394765639">"Paslauga įgalinta."</string>
diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml
index b57de7772e45..792ce6a37a89 100644
--- a/core/res/res/values-lv/strings.xml
+++ b/core/res/res/values-lv/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Balss pasts"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"Savienojuma problēma vai nederīgs MMI kods."</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Funkcija netiek atbalstīta."</string>
<string name="mmiFdnError" msgid="3975490266767565852">"Darbība ir atļauta tikai fiksēto numuru sastādīšanai."</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Nevar mainīt zvanu pāradresēšanas iestatījumus tālrunī, kamēr izmantojat viesabonēšanu."</string>
<string name="serviceEnabled" msgid="7549025003394765639">"Pakalpojums tika iespējots."</string>
diff --git a/core/res/res/values-mk/strings.xml b/core/res/res/values-mk/strings.xml
index 51a6055ebab5..e5710cd8c9bd 100644
--- a/core/res/res/values-mk/strings.xml
+++ b/core/res/res/values-mk/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Говорна пошта"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"Проблем со поврзување или неважечки MMI код."</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Функцијата не е поддржана."</string>
<string name="mmiFdnError" msgid="3975490266767565852">"Операцијата е ограничена на бирање само фиксни броеви."</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Не може да се сменат поставките за проследување повик од телефонот додека сте во роаминг."</string>
<string name="serviceEnabled" msgid="7549025003394765639">"Услугата беше овозможена."</string>
diff --git a/core/res/res/values-ml/strings.xml b/core/res/res/values-ml/strings.xml
index 1b9fc946422a..65b2c5d37137 100644
--- a/core/res/res/values-ml/strings.xml
+++ b/core/res/res/values-ml/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"വോയ്സ് മെയില്‍"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"കണക്ഷൻ പ്രശ്‌നം അല്ലെങ്കിൽ MMI കോഡ് അസാധുവാണ്."</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"ഫീച്ചർ പിന്തുണയ്‌ക്കുന്നില്ല."</string>
<string name="mmiFdnError" msgid="3975490266767565852">"നിശ്ചയിച്ചുറപ്പിച്ച ഡയൽ ചെയ്യൽ നമ്പറുകൾക്ക് മാത്രമായി പ്രവർത്തനം പരിമിതപ്പെടുത്തിയിരിക്കുന്നു."</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"റോമിംഗിൽ ആയിരിക്കുമ്പോൾ നിങ്ങളുടെ ഫോണിൽ നിന്ന് കോൾ കൈമാറ്റ ക്രമീകരണം സാധിക്കില്ല."</string>
<string name="serviceEnabled" msgid="7549025003394765639">"സേവനം പ്രവർത്തനക്ഷമമാക്കി."</string>
diff --git a/core/res/res/values-mn/strings.xml b/core/res/res/values-mn/strings.xml
index 0fd3c6f28534..6d48c97682d8 100644
--- a/core/res/res/values-mn/strings.xml
+++ b/core/res/res/values-mn/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"дуут шуудан"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"Холболтын асуудал эсвэл буруу MMI код."</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Онцлогийг дэмжээгүй."</string>
<string name="mmiFdnError" msgid="3975490266767565852">"Ажиллагаа зөвөх тогтсон дугаараар хязгаарлагдсан."</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Таныг роуминг үйлчилгээг идэвхжүүлсэн үед таны утаснаас дуудлага дамжуулах тохиргоог өөрчлөх боломжгүй."</string>
<string name="serviceEnabled" msgid="7549025003394765639">"Үйлчилгээ идэвхжсэн."</string>
diff --git a/core/res/res/values-mr/strings.xml b/core/res/res/values-mr/strings.xml
index a4121dea4ab6..b33fb3fbf168 100644
--- a/core/res/res/values-mr/strings.xml
+++ b/core/res/res/values-mr/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"व्हॉइसमेल"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"कनेक्शन समस्या किंवा अवैध MMI कोड."</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"वैशिष्ट्याला सपोर्ट नाही."</string>
<string name="mmiFdnError" msgid="3975490266767565852">"कार्य फक्त निश्चित डायलिंग नंबरसाठी प्रतिबंधित आहे."</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"तुम्ही रोमिंगमध्ये असताना आपल्या फोनवरील कॉल फॉरवर्डिंग सेटिंंग्ज बदलू शकत नाही."</string>
<string name="serviceEnabled" msgid="7549025003394765639">"सेवा सक्षम केली."</string>
diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml
index 5faec0a244f6..c6065c6b6295 100644
--- a/core/res/res/values-ms/strings.xml
+++ b/core/res/res/values-ms/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Mel suara"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"Masalah sambungan atau kod MMI tidak sah"</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Ciri tidak disokong."</string>
<string name="mmiFdnError" msgid="3975490266767565852">"Pengendalian dihadkan kepada nombor dailan tetap sahaja."</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Tidak dapat mengubah tetapan pemajuan panggilan daripad telefon anda semasa dalam perayauan."</string>
<string name="serviceEnabled" msgid="7549025003394765639">"Perkhidmatan telah didayakan."</string>
diff --git a/core/res/res/values-my/strings.xml b/core/res/res/values-my/strings.xml
index 6f45b9383905..0e92d8a7ee67 100644
--- a/core/res/res/values-my/strings.xml
+++ b/core/res/res/values-my/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"အသံမေးလ်"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"ဆက်သွယ်မှုဆိုင်ရာပြသနာ သို့မဟုတ် မမှန်ကန်သောMMIကုတ်"</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"ဝန်ဆောင်မှုကို မပံ့ပိုးပါ။"</string>
<string name="mmiFdnError" msgid="3975490266767565852">"သတ်မှတ်ခေါ်ဆိုနိုင်သောနံပါတ်များထံသာ ကန့်သတ်ထားသည်"</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"ကွန်ရက်ပြင်ပဒေတာအသုံးပြုခြင်းကို ဖွင့်ထားသည့်အခါ သင့်ဖုန်းမှနေ၍ ခေါ်ဆိုမှုထပ်ဆင့်ပို့ခြင်းဆက်တင်အား ပြောင်း၍မရပါ။"</string>
<string name="serviceEnabled" msgid="7549025003394765639">"ဝန်ဆောင်မှု လုပ်ဆောင်နိုင်မည်"</string>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index 0a940f1cf405..8b796ddb5fbc 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Telefonsvarer"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"Tilkoblingsproblem eller ugyldig MMI-kode."</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Funksjonen støttes ikke."</string>
<string name="mmiFdnError" msgid="3975490266767565852">"Handlingen kan kun utføres på numre med anropsbegrensning."</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Får ikke endret innstillinger for viderekobling fra telefonen din når du bruker roaming."</string>
<string name="serviceEnabled" msgid="7549025003394765639">"Tjenesten ble aktivert."</string>
diff --git a/core/res/res/values-ne/strings.xml b/core/res/res/values-ne/strings.xml
index 9b25f8a4cf81..54875acec440 100644
--- a/core/res/res/values-ne/strings.xml
+++ b/core/res/res/values-ne/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"भ्वाइस मेल"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN१"</string>
<string name="mmiError" msgid="2862759606579822246">"जडान समस्या वा अमान्य MMI कोड।"</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"यो सुविधा प्रयोग गर्न मिल्दैन।"</string>
<string name="mmiFdnError" msgid="3975490266767565852">"अपरेशन निश्चित डायल नम्बरहरूको लागि मात्र प्रतिबन्धित छ।"</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"तपाईं रोमिङमा हुनुहुँदा तपाईंको फोनबाट कल फर्वार्ड गर्ने सम्बन्धी सेटिङहरू परिवर्तन गर्न सकिँदैन।"</string>
<string name="serviceEnabled" msgid="7549025003394765639">"सेवा सक्षम पारियो।"</string>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index 07413e6b4e30..e40a087aefdc 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Voicemail"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"Verbindingsprobleem of ongeldige MMI-code."</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Functie niet ondersteund."</string>
<string name="mmiFdnError" msgid="3975490266767565852">"Bewerking is beperkt tot vaste nummers."</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Kan instellingen voor doorschakelen van gesprekken niet wijzigen vanaf je telefoon tijdens roaming."</string>
<string name="serviceEnabled" msgid="7549025003394765639">"Service staat aan."</string>
@@ -257,7 +258,7 @@
<string name="global_action_toggle_silent_mode" msgid="8464352592860372188">"Stille modus"</string>
<string name="global_action_silent_mode_on_status" msgid="2371892537738632013">"Geluid is UIT"</string>
<string name="global_action_silent_mode_off_status" msgid="6608006545950920042">"Geluid is AAN"</string>
- <string name="global_actions_toggle_airplane_mode" msgid="6911684460146916206">"Vliegtuigmodus"</string>
+ <string name="global_actions_toggle_airplane_mode" msgid="6911684460146916206">"Vliegtuig­modus"</string>
<string name="global_actions_airplane_mode_on_status" msgid="5508025516695361936">"Vliegtuigmodus is AAN"</string>
<string name="global_actions_airplane_mode_off_status" msgid="8522219771500505475">"Vliegtuigmodus is UIT"</string>
<string name="global_action_settings" msgid="4671878836947494217">"Instellingen"</string>
@@ -1016,7 +1017,7 @@
<string name="keyguard_accessibility_user_selector" msgid="1466067610235696600">"Gebruikersselectie"</string>
<string name="keyguard_accessibility_status" msgid="6792745049712397237">"Status"</string>
<string name="keyguard_accessibility_camera" msgid="7862557559464986528">"Camera"</string>
- <string name="keygaurd_accessibility_media_controls" msgid="2267379779900620614">"Mediabediening"</string>
+ <string name="keygaurd_accessibility_media_controls" msgid="2267379779900620614">"Media­bediening"</string>
<string name="keyguard_accessibility_widget_reorder_start" msgid="7066213328912939191">"Opnieuw indelen van widget gestart."</string>
<string name="keyguard_accessibility_widget_reorder_end" msgid="1083806817600593490">"Opnieuw indelen van widget beëindigd."</string>
<string name="keyguard_accessibility_widget_deleted" msgid="1509738950119878705">"Widget <xliff:g id="WIDGET_INDEX">%1$s</xliff:g> verwijderd."</string>
@@ -1509,7 +1510,7 @@
<string name="forward_intent_to_work" msgid="3620262405636021151">"U gebruikt deze app in je werkprofiel"</string>
<string name="input_method_binding_label" msgid="1166731601721983656">"Invoermethode"</string>
<string name="sync_binding_label" msgid="469249309424662147">"Synchroniseren"</string>
- <string name="accessibility_binding_label" msgid="1974602776545801715">"Toegankelijkheid"</string>
+ <string name="accessibility_binding_label" msgid="1974602776545801715">"Toe­gankelijk­heid"</string>
<string name="wallpaper_binding_label" msgid="1197440498000786738">"Achtergrond"</string>
<string name="chooser_wallpaper" msgid="3082405680079923708">"Achtergrond wijzigen"</string>
<string name="notification_listener_binding_label" msgid="2702165274471499713">"Listener voor meldingen"</string>
@@ -2015,7 +2016,7 @@
<string name="app_category_news" msgid="1172762719574964544">"Nieuws en tijdschriften"</string>
<string name="app_category_maps" msgid="6395725487922533156">"Maps en navigatie"</string>
<string name="app_category_productivity" msgid="1844422703029557883">"Productiviteit"</string>
- <string name="app_category_accessibility" msgid="6643521607848547683">"Toegankelijkheid"</string>
+ <string name="app_category_accessibility" msgid="6643521607848547683">"Toe­gankelijk­heid"</string>
<string name="device_storage_monitor_notification_channel" msgid="5164244565844470758">"Apparaatopslag"</string>
<string name="adb_debugging_notification_channel_tv" msgid="4764046459631031496">"USB-foutopsporing"</string>
<string name="time_picker_hour_label" msgid="4208590187662336864">"uur"</string>
diff --git a/core/res/res/values-or/strings.xml b/core/res/res/values-or/strings.xml
index 5206994026db..412409a47e1e 100644
--- a/core/res/res/values-or/strings.xml
+++ b/core/res/res/values-or/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"ଭଏସ୍‌ ମେଲ୍"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"ସଂଯୋଗରେ ସମସ୍ୟା ଅଛି କିମ୍ବା ଅମାନ୍ୟ MMI କୋଡ୍।"</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"ଫିଚର ସମର୍ଥିତ ନୁହେଁ।"</string>
<string name="mmiFdnError" msgid="3975490266767565852">"କେବଳ ସ୍ଥାୟୀ ଡାୟଲିଙ୍ଗ ନମ୍ବର୍‌ ପାଇଁ କାର୍ଯ୍ୟ ସୀମିତ ଅଟେ।"</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"ଆପଣ ରୋମିଙ୍ଗରେ ଥିବାବେଳେ କଲ୍‍ ଫର୍‌ୱର୍ଡିଙ୍ଗ ସେଟିଙ୍ଗ ବଦଳାଇପାରିବେ ନାହିଁ।"</string>
<string name="serviceEnabled" msgid="7549025003394765639">"ସେବା ସକ୍ଷମ କରାଯାଇଥିଲା।"</string>
@@ -87,7 +88,7 @@
<string name="notification_channel_network_alert" msgid="4788053066033851841">"ଆଲର୍ଟ"</string>
<string name="notification_channel_call_forward" msgid="8230490317314272406">"କଲ୍‌ ଫରୱାର୍ଡିଂ"</string>
<string name="notification_channel_emergency_callback" msgid="54074839059123159">"ଜରୁରୀକାଳୀନ କଲବ୍ୟାକ୍‍ ମୋଡ୍‍"</string>
- <string name="notification_channel_mobile_data_status" msgid="1941911162076442474">"ମୋବାଇଲ୍‍ ଡାଟା ଷ୍ଟାଟସ୍‌"</string>
+ <string name="notification_channel_mobile_data_status" msgid="1941911162076442474">"ମୋବାଇଲ ଡାଟା ଷ୍ଟାଟସ"</string>
<string name="notification_channel_sms" msgid="1243384981025535724">"SMS ମେସେଜ୍‌"</string>
<string name="notification_channel_voice_mail" msgid="8457433203106654172">"ଭଏସମେଲ୍‍ ମେସେଜ୍‍"</string>
<string name="notification_channel_wfc" msgid="9048240466765169038">"ୱାଇ-ଫାଇ କଲିଙ୍ଗ"</string>
@@ -825,30 +826,30 @@
<item msgid="8150904584178569699">"ୱାର୍କ ଫ୍ୟାକ୍ସ"</item>
<item msgid="4537253139152229577">"ହୋମ ଫାକ୍ସ"</item>
<item msgid="6751245029698664340">"ପେଜର୍"</item>
- <item msgid="1692790665884224905">"ଅନ୍ୟାନ୍ୟ"</item>
+ <item msgid="1692790665884224905">"ଅନ୍ୟ"</item>
<item msgid="6216981255272016212">"କଷ୍ଟମ୍‌"</item>
</string-array>
<string-array name="emailAddressTypes">
<item msgid="7786349763648997741">"ହୋମ"</item>
<item msgid="435564470865989199">"ୱାର୍କ"</item>
- <item msgid="4199433197875490373">"ଅନ୍ୟାନ୍ୟ"</item>
+ <item msgid="4199433197875490373">"ଅନ୍ୟ"</item>
<item msgid="3233938986670468328">"କଷ୍ଟମ୍‌"</item>
</string-array>
<string-array name="postalAddressTypes">
<item msgid="3861463339764243038">"ହୋମ"</item>
<item msgid="5472578890164979109">"ୱାର୍କ"</item>
- <item msgid="5718921296646594739">"ଅନ୍ୟାନ୍ୟ"</item>
+ <item msgid="5718921296646594739">"ଅନ୍ୟ"</item>
<item msgid="5523122236731783179">"କଷ୍ଟମ୍‌"</item>
</string-array>
<string-array name="imAddressTypes">
<item msgid="588088543406993772">"ହୋମ"</item>
<item msgid="5503060422020476757">"ୱାର୍କ"</item>
- <item msgid="2530391194653760297">"ଅନ୍ୟାନ୍ୟ"</item>
+ <item msgid="2530391194653760297">"ଅନ୍ୟ"</item>
<item msgid="7640927178025203330">"କଷ୍ଟମ୍‌"</item>
</string-array>
<string-array name="organizationTypes">
<item msgid="6144047813304847762">"ୱାର୍କ"</item>
- <item msgid="7402720230065674193">"ଅନ୍ୟାନ୍ୟ"</item>
+ <item msgid="7402720230065674193">"ଅନ୍ୟ"</item>
<item msgid="808230403067569648">"କଷ୍ଟମ୍‌"</item>
</string-array>
<string-array name="imProtocols">
@@ -868,7 +869,7 @@
<string name="phoneTypeFaxWork" msgid="6757519896109439123">"ୱାର୍କ ଫାକ୍ସ"</string>
<string name="phoneTypeFaxHome" msgid="6678559953115904345">"ହୋମ ଫାକ୍ସ"</string>
<string name="phoneTypePager" msgid="576402072263522767">"ପେଜର୍"</string>
- <string name="phoneTypeOther" msgid="6918196243648754715">"ଅନ୍ୟାନ୍ୟ"</string>
+ <string name="phoneTypeOther" msgid="6918196243648754715">"ଅନ୍ୟ"</string>
<string name="phoneTypeCallback" msgid="3455781500844157767">"କଲବ୍ୟାକ୍"</string>
<string name="phoneTypeCar" msgid="4604775148963129195">"କାର୍"</string>
<string name="phoneTypeCompanyMain" msgid="4482773154536455441">"କମ୍ପାନୀର ମୁଖ୍ୟ"</string>
@@ -889,16 +890,16 @@
<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="emailTypeOther" msgid="5131130857030897465">"ଅନ୍ୟ"</string>
<string name="emailTypeMobile" msgid="787155077375364230">"ମୋବାଇଲ୍‍"</string>
<string name="postalTypeCustom" msgid="5645590470242939129">"କଷ୍ଟମ୍‌"</string>
<string name="postalTypeHome" msgid="7562272480949727912">"ହୋମ"</string>
<string name="postalTypeWork" msgid="8553425424652012826">"ୱାର୍କ"</string>
- <string name="postalTypeOther" msgid="7094245413678857420">"ଅନ୍ୟାନ୍ୟ"</string>
+ <string name="postalTypeOther" msgid="7094245413678857420">"ଅନ୍ୟ"</string>
<string name="imTypeCustom" msgid="5653384545085765570">"କଷ୍ଟମ୍‌"</string>
<string name="imTypeHome" msgid="6996507981044278216">"ହୋମ"</string>
<string name="imTypeWork" msgid="2099668940169903123">"ୱାର୍କ"</string>
- <string name="imTypeOther" msgid="8068447383276219810">"ଅନ୍ୟାନ୍ୟ"</string>
+ <string name="imTypeOther" msgid="8068447383276219810">"ଅନ୍ୟ"</string>
<string name="imProtocolCustom" msgid="4437878287653764692">"କଷ୍ଟମ୍‌"</string>
<string name="imProtocolAim" msgid="4050198236506604378">"AIM"</string>
<string name="imProtocolMsn" msgid="2257148557766499232">"Windows Live"</string>
@@ -910,7 +911,7 @@
<string name="imProtocolJabber" msgid="7919269388889582015">"Jabber"</string>
<string name="imProtocolNetMeeting" msgid="4985002408136148256">"NetMeeting"</string>
<string name="orgTypeWork" msgid="8684458700669564172">"ୱାର୍କ"</string>
- <string name="orgTypeOther" msgid="5450675258408005553">"ଅନ୍ୟାନ୍ୟ"</string>
+ <string name="orgTypeOther" msgid="5450675258408005553">"ଅନ୍ୟ"</string>
<string name="orgTypeCustom" msgid="1126322047677329218">"କଷ୍ଟମ୍‌"</string>
<string name="relationTypeCustom" msgid="282938315217441351">"କଷ୍ଟମ୍‌"</string>
<string name="relationTypeAssistant" msgid="4057605157116589315">"Assistant"</string>
@@ -930,7 +931,7 @@
<string name="sipAddressTypeCustom" msgid="6283889809842649336">"କଷ୍ଟମ୍‌"</string>
<string name="sipAddressTypeHome" msgid="5918441930656878367">"ହୋମ"</string>
<string name="sipAddressTypeWork" msgid="7873967986701216770">"ୱାର୍କ"</string>
- <string name="sipAddressTypeOther" msgid="6317012577345187275">"ଅନ୍ୟାନ୍ୟ"</string>
+ <string name="sipAddressTypeOther" msgid="6317012577345187275">"ଅନ୍ୟ"</string>
<string name="quick_contacts_not_available" msgid="1262709196045052223">"ଏହି କଣ୍ଟାକ୍ଟ ଦେଖିବାକୁ କୌଣସି ଆପ୍ଲିକେସନ ମିଳିଲା ନାହିଁ।"</string>
<string name="keyguard_password_enter_pin_code" msgid="6401406801060956153">"PIN କୋଡ୍‍ ଟାଇପ୍‍ କରନ୍ତୁ"</string>
<string name="keyguard_password_enter_puk_code" msgid="3112256684547584093">"PUK ଓ ନୂଆ PIN କୋଡ୍‍ ଟାଇପ୍‍ କରନ୍ତୁ"</string>
diff --git a/core/res/res/values-pa/strings.xml b/core/res/res/values-pa/strings.xml
index ace0d94a8b59..cb2886e636c4 100644
--- a/core/res/res/values-pa/strings.xml
+++ b/core/res/res/values-pa/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"ਵੌਇਸਮੇਲ"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"ਕਨੈਕਸ਼ਨ ਸਮੱਸਿਆ ਜਾਂ ਅਵੈਧ MMI ਕੋਡ।"</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"ਵਿਸ਼ੇਸ਼ਤਾ ਸਮਰਥਿਤ ਨਹੀਂ ਹੈ।"</string>
<string name="mmiFdnError" msgid="3975490266767565852">"ਓਪਰੇਸ਼ਨ ਕੇਵਲ ਫਿਕਸਡ ਡਾਇਲਿੰਗ ਨੰਬਰਾਂ ਤੱਕ ਸੀਮਿਤ ਹੈ।"</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"ਤੁਹਾਡੇ ਰੋਮਿੰਗ ਵਿੱਚ ਹੋਣ ਦੌਰਾਨ ਤੁਹਾਡੇ ਫ਼ੋਨ ਤੋਂ ਕਾਲ ਫਾਰਵਰਡਿੰਗ ਸੈਟਿੰਗਾਂ ਨੂੰ ਬਦਲਿਆ ਨਹੀਂ ਜਾ ਸਕਦਾ।"</string>
<string name="serviceEnabled" msgid="7549025003394765639">"ਸੇਵਾ ਅਸਮਰੱਥ ਬਣਾਈ ਗਈ ਸੀ।"</string>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index a62873ee494a..70c9e32b79da 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Poczta głosowa"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"Problem z połączeniem lub błędny kod MMI."</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Funkcja nie jest obsługiwana."</string>
<string name="mmiFdnError" msgid="3975490266767565852">"Operacja jest ograniczona wyłącznie do numerów ustalonych."</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Podczas roamingu nie można zmienić ustawień przekazywania połączeń z telefonu."</string>
<string name="serviceEnabled" msgid="7549025003394765639">"Usługa została włączona."</string>
@@ -397,54 +398,30 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Pozwala aplikacji na trwałe zapisywanie swoich fragmentów w pamięci. Może to zmniejszyć ilość pamięci dostępnej dla innych aplikacji i spowolnić działanie telefonu."</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"uruchom usługę na pierwszym planie"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"Zezwala na korzystanie przez aplikację z usług na pierwszym planie."</string>
- <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
- <skip />
- <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
- <skip />
- <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
- <skip />
- <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
- <skip />
- <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
- <skip />
- <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
- <skip />
- <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
- <skip />
- <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
- <skip />
- <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
- <skip />
- <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
- <skip />
- <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
- <skip />
- <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
- <skip />
+ <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"uruchamianie usług działających na pierwszym planie typu „camera”"</string>
+ <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"Zezwala na wykorzystywanie przez aplikację usług działających na pierwszym planie typu „camera”"</string>
+ <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"uruchamianie usług działających na pierwszym planie typu „connectedDevice”"</string>
+ <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"Zezwala na wykorzystywanie przez aplikację usług działających na pierwszym planie typu „connectedDevice”"</string>
+ <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"uruchamianie usług działających na pierwszym planie typu „dataSync”"</string>
+ <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"Zezwala na wykorzystywanie przez aplikację usług działających na pierwszym planie typu „dataSync”"</string>
+ <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"uruchamianie usług działających na pierwszym planie typu „location”"</string>
+ <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"Zezwala na wykorzystywanie przez aplikację usług działających na pierwszym planie typu „location”"</string>
+ <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"uruchamianie usług działających na pierwszym planie typu „mediaPlayback”"</string>
+ <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"Zezwala na wykorzystywanie przez aplikację usług działających na pierwszym planie typu „mediaPlayback”"</string>
+ <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"uruchamianie usług działających na pierwszym planie typu „mediaProjection”"</string>
+ <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"Zezwala na wykorzystywanie przez aplikację usług działających na pierwszym planie typu „mediaProjection”"</string>
+ <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"uruchamianie usług działających na pierwszym planie typu „microphone”"</string>
+ <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"Zezwala na wykorzystywanie przez aplikację usług działających na pierwszym planie typu „microphone”"</string>
+ <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"uruchamianie usług działających na pierwszym planie typu „phoneCall”"</string>
+ <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"Zezwala na wykorzystywanie przez aplikację usług działających na pierwszym planie typu „phoneCall”"</string>
+ <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"uruchamianie usług działających na pierwszym planie typu „health”"</string>
+ <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"Zezwala na wykorzystywanie przez aplikację usług działających na pierwszym planie typu „health”"</string>
+ <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"uruchamianie usług działających na pierwszym planie typu „remoteMessaging”"</string>
+ <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"Zezwala na wykorzystywanie przez aplikację usług działających na pierwszym planie typu „remoteMessaging”"</string>
+ <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"uruchamianie usług działających na pierwszym planie typu „systemExempted”"</string>
+ <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Zezwala na wykorzystywanie przez aplikację usług działających na pierwszym planie typu „systemExempted”"</string>
+ <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"uruchamianie usług działających na pierwszym planie typu „specialUse”"</string>
+ <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Zezwala na wykorzystywanie przez aplikację usług działających na pierwszym planie typu „specialUse”"</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"mierzenie rozmiaru pamięci aplikacji"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"Pozwala aplikacji na pobieranie własnego kodu, danych oraz rozmiarów pamięci podręcznej."</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"modyfikowanie ustawień systemu"</string>
diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml
index a9830e71623c..255ed46eec05 100644
--- a/core/res/res/values-pt-rBR/strings.xml
+++ b/core/res/res/values-pt-rBR/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Correio de voz"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"Problema de conexão ou código MMI inválido."</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Recurso indisponível."</string>
<string name="mmiFdnError" msgid="3975490266767565852">"A operação é limitada somente a números de discagem fixa."</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Não é possível alterar as configurações de encaminhamento de chamada do seu smartphone em roaming."</string>
<string name="serviceEnabled" msgid="7549025003394765639">"O serviço foi ativado."</string>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index e2966a2bebc2..480de8747051 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Correio de voz"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"Problema de ligação ou código MMI inválido."</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Funcionalidade não suportada."</string>
<string name="mmiFdnError" msgid="3975490266767565852">"A operação está restringida a números fixos autorizados."</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Não é possível alterar as definições do encaminhamento de chamadas no telemóvel quando está em roaming."</string>
<string name="serviceEnabled" msgid="7549025003394765639">"O serviço foi ativado."</string>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index a9830e71623c..255ed46eec05 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Correio de voz"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"Problema de conexão ou código MMI inválido."</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Recurso indisponível."</string>
<string name="mmiFdnError" msgid="3975490266767565852">"A operação é limitada somente a números de discagem fixa."</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Não é possível alterar as configurações de encaminhamento de chamada do seu smartphone em roaming."</string>
<string name="serviceEnabled" msgid="7549025003394765639">"O serviço foi ativado."</string>
diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml
index d300dfa612bf..4d8d96b4fe87 100644
--- a/core/res/res/values-ro/strings.xml
+++ b/core/res/res/values-ro/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Mesagerie vocală"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"Problemă de conexiune sau cod MMI nevalid."</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Funcția nu este acceptată."</string>
<string name="mmiFdnError" msgid="3975490266767565852">"Operația este limitată la numerele cu apelări restricționate."</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Nu poți schimba setările de redirecționare a apelurilor de pe telefon când ești în roaming."</string>
<string name="serviceEnabled" msgid="7549025003394765639">"Serviciul a fost activat."</string>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index 36fa51bc67d7..353131e02cfd 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Голосовая почта"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"Неполадки подключения или неверный код MMI."</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Функция не поддерживается."</string>
<string name="mmiFdnError" msgid="3975490266767565852">"Операция возможна только для разрешенных номеров."</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Вы не можете изменить настройки переадресации вызовов, поскольку находитесь в роуминге."</string>
<string name="serviceEnabled" msgid="7549025003394765639">"Служба включена."</string>
@@ -89,7 +90,7 @@
<string name="notification_channel_network_alert" msgid="4788053066033851841">"Оповещения"</string>
<string name="notification_channel_call_forward" msgid="8230490317314272406">"Переадресация вызовов"</string>
<string name="notification_channel_emergency_callback" msgid="54074839059123159">"Режим экстренных обратных вызовов"</string>
- <string name="notification_channel_mobile_data_status" msgid="1941911162076442474">"Состояние мобильного Интернета"</string>
+ <string name="notification_channel_mobile_data_status" msgid="1941911162076442474">"Состояние мобильного интернета"</string>
<string name="notification_channel_sms" msgid="1243384981025535724">"SMS"</string>
<string name="notification_channel_voice_mail" msgid="8457433203106654172">"Голосовые сообщения"</string>
<string name="notification_channel_wfc" msgid="9048240466765169038">"Звонки по Wi-Fi"</string>
diff --git a/core/res/res/values-si/strings.xml b/core/res/res/values-si/strings.xml
index 4f9fb412f33d..7bee4de3dfc7 100644
--- a/core/res/res/values-si/strings.xml
+++ b/core/res/res/values-si/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"කටහඬ තැපෑල"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"සම්බන්ධතා ගැටළුවක් හෝ අවලංගු MMI කේතයකි."</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"විශේෂාංගය සහාය නොදක්වයි."</string>
<string name="mmiFdnError" msgid="3975490266767565852">"ස්ථාවර ඇමතීම් අංක වලට පමණක් මෙහෙයුම සීමාකර ඇත."</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"ඔබ රෝමිං තුළ සිටින අතරතුර ඔබේ දුරකථනයෙන් ඇමතුම් ප්‍රතියොමු සැකසීම් වෙනස් කළ නොහැකිය."</string>
<string name="serviceEnabled" msgid="7549025003394765639">"සේවාව සබල කරන ලදි."</string>
diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml
index 74d11ff385c4..7fa87e45544e 100644
--- a/core/res/res/values-sk/strings.xml
+++ b/core/res/res/values-sk/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Hlasová schránka"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"Problém s pripojením alebo neplatný kód MMI."</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Funkcia nie je podporovaná."</string>
<string name="mmiFdnError" msgid="3975490266767565852">"Operácia je obmedzená len na povolené čísla."</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Nastavenia presmerovania hovorov nie je možné zmeniť z telefónu počas roamingu."</string>
<string name="serviceEnabled" msgid="7549025003394765639">"Služba bola povolená."</string>
diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml
index 0111719e1cff..d98a13043503 100644
--- a/core/res/res/values-sl/strings.xml
+++ b/core/res/res/values-sl/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Glasovna pošta"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"Težava s povezavo ali neveljavna koda MMI."</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Funkcija ni podprta."</string>
<string name="mmiFdnError" msgid="3975490266767565852">"Operacija je omejena na dovoljene telefonske številke, za katere ne velja zapora odhodnega klica."</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Nastavitev preusmerjanja klicev ni mogoče spremeniti v telefonu med gostovanjem v tujem omrežju."</string>
<string name="serviceEnabled" msgid="7549025003394765639">"Storitev je omogočena."</string>
diff --git a/core/res/res/values-sq/strings.xml b/core/res/res/values-sq/strings.xml
index 799f483d66ed..17c1f63a25a8 100644
--- a/core/res/res/values-sq/strings.xml
+++ b/core/res/res/values-sq/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Posta zanore"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"Problem në lidhje ose kod i pavlefshëm MMI-je."</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Veçoria nuk mbështetet."</string>
<string name="mmiFdnError" msgid="3975490266767565852">"Veprimi është i kufizuar vetëm kundrejt numrave me telefonim të përzgjedhur"</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Cilësimet e transferimit të telefonatave nuk mund të ndryshohen nga telefoni yt kur je në roaming."</string>
<string name="serviceEnabled" msgid="7549025003394765639">"Shërbimi u aktivizua."</string>
diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml
index f5de28489e1f..e7b1b12a3c68 100644
--- a/core/res/res/values-sr/strings.xml
+++ b/core/res/res/values-sr/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Гласовна пошта"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"Проблеми са везом или неважећи MMI кôд."</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Функција није подржана."</string>
<string name="mmiFdnError" msgid="3975490266767565852">"Рад је ограничен само на бројеве фиксног бирања."</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Не можете да промените подешавања преусмеравања позива са телефона док сте у ромингу."</string>
<string name="serviceEnabled" msgid="7549025003394765639">"Услуга је омогућена."</string>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index dec226a5effe..7d4ba7497efb 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Röstbrevlåda"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"Anslutningsproblem eller ogiltig MMI-kod."</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Funktionen stöds inte."</string>
<string name="mmiFdnError" msgid="3975490266767565852">"Endast fasta nummer kan användas."</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Det går inte att ändra inställningarna för vidarebefordran av samtal medan mobilen är i roaming-läge."</string>
<string name="serviceEnabled" msgid="7549025003394765639">"Tjänsten har aktiverats."</string>
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index a68fcc8cf807..702e97feed87 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Ujumbe wa sauti"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"Tatizo la muunganisho au msimbo batili MMI."</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Kipengele hakitumiki."</string>
<string name="mmiFdnError" msgid="3975490266767565852">"Ni matumizi yanayohusisha nambari za simu zilizobainishwa pekee yatakayowezekana."</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Haiwezi kubadilisha mipangilio ya kusambaza simu kutoka kwenye simu yako ukiwa unatumia mitandao mingine."</string>
<string name="serviceEnabled" msgid="7549025003394765639">"Huduma iliwezeshwa"</string>
@@ -395,54 +396,30 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Inaruhusu programu kuendelesha vijisehemu vyake kwenye kumbukumbu. Hii inaweza kupunguza kumbukumbu inayopatikana katika programu nyingine ikipunguza kasi ya simu."</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"tumia huduma zinazoonekana kwenye skrini"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"Huruhusu programu kutumia huduma zinazoonekana kwenye skrini."</string>
- <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
- <skip />
- <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
- <skip />
- <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
- <skip />
- <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
- <skip />
- <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
- <skip />
- <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
- <skip />
- <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
- <skip />
- <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
- <skip />
- <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
- <skip />
- <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
- <skip />
- <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
- <skip />
- <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
- <skip />
+ <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"kutekeleza huduma inayoonekana kwenye skrini inayohusiana na \"camera\""</string>
+ <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"Huruhusu programu itumie huduma zinazoonekana kwenye skrini zinazohusiana na \"camera\""</string>
+ <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"kutekeleza huduma inayoonekana kwenye skrini inayohusiana na \"connectedDevice\""</string>
+ <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"Huruhusu programu itumie huduma zinazoonekana kwenye skrini zinazohusiana na \"connectedDevice\""</string>
+ <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"kutekeleza huduma inayoonekana kwenye skrini inayohusiana na \"dataSync\""</string>
+ <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"Huruhusu programu itumie huduma zinazoonekana kwenye skrini zinazohusiana na \"dataSync\""</string>
+ <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"kutekeleza huduma inayoonekana kwenye skrini inayohusiana na \"location\""</string>
+ <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"Huruhusu programu itumie huduma zinazoonekana kwenye skrini zinazohusiana na \"health\""</string>
+ <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"kutekeleza huduma inayoonekana kwenye skrini inayohusiana na \"mediaPlayback\""</string>
+ <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"Huruhusu programu itumie huduma zinazoonekana kwenye skrini zinazohusiana na \"mediaPlayback\""</string>
+ <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"kutekeleza huduma inayoonekana kwenye skrini inayohusiana na \"mediaProjection\""</string>
+ <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"Huruhusu programu itumie huduma zinazoonekana kwenye skrini zinazohusiana na \"mediaProjection\""</string>
+ <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"kutekeleza huduma inayoonekana kwenye skrini inayohusiana na \"microphone\""</string>
+ <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"Huruhusu programu itumie huduma zinazoonekana kwenye skrini zinazohusiana na \"microphone\""</string>
+ <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"kutekeleza huduma inayoonekana kwenye skrini inayohusiana na \"phoneCall\""</string>
+ <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"Huruhusu programu itumie huduma zinazoonekana kwenye skrini zinazohusiana na \"phoneCall\""</string>
+ <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"kutekeleza huduma inayoonekana kwenye skrini inayohusiana na \"health\""</string>
+ <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"Huruhusu programu itumie huduma zinazoonekana kwenye skrini zinazohusiana na \"health\""</string>
+ <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"kutekeleza huduma inayoonekana kwenye skrini inayohusiana na \"remoteMessaging\""</string>
+ <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"Huruhusu programu itumie huduma zinazoonekana kwenye skrini zinazohusiana na \"remoteMessaging\""</string>
+ <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"kutekeleza huduma inayoonekana kwenye skrini inayohusiana na \"systemExempted\""</string>
+ <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Huruhusu programu itumie huduma zinazoonekana kwenye skrini zinazohusiana na \"systemExempted\""</string>
+ <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"kutekeleza huduma inayoonekana kwenye skrini inayohusiana na \"specialUse\""</string>
+ <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Huruhusu programu itumie huduma zinazoonekana kwenye skrini zinazohusiana na \"specialUse\""</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"Pima nafasi ya hifadhi ya programu"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"Huruhusu Programu kupata tena msimbo, data na ukubwa wa akiba yake"</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"rekebisha mipangilio ya mfumo"</string>
diff --git a/core/res/res/values-ta/strings.xml b/core/res/res/values-ta/strings.xml
index 20875bb85753..7e1ad9e55722 100644
--- a/core/res/res/values-ta/strings.xml
+++ b/core/res/res/values-ta/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"குரலஞ்சல்"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"இணைப்பு சிக்கல் அல்லது தவறான MMI குறியீடு."</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"அம்சம் ஆதரிக்கப்படவில்லை."</string>
<string name="mmiFdnError" msgid="3975490266767565852">"நிலையான அழைப்பு எண்களுக்கு மட்டுமே எனச் செயல்பாடு வரையறுக்கப்பட்டுள்ளது."</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"ரோமிங்கில் இருக்கும் போது, உங்கள் மொபைலிலிருந்து அழைப்புப் பகிர்வு அமைப்புகளை மாற்ற முடியாது."</string>
<string name="serviceEnabled" msgid="7549025003394765639">"சேவை இயக்கப்பட்டுள்ளது."</string>
@@ -395,54 +396,30 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"நினைவகத்தில் நிலையாக இருக்கும் தன்னுடைய பகுதிகளை உருவாக்கப் ஆப்ஸை அனுமதிக்கிறது. இதனால பிற பயன்பாடுகளுக்குக் கிடைக்கும் நினைவகம் வரையறுக்கப்பட்டு, மொபைலின் வேகத்தைக் குறைக்கலாம்"</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"முன்புலத்தில் இயங்கும் சேவையை இயக்குதல்"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"முன்புலத்தில் இயங்கும் சேவைகளை உபயோகிக்க, ஆப்ஸை அனுமதிக்கிறது."</string>
- <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
- <skip />
- <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
- <skip />
- <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
- <skip />
- <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
- <skip />
- <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
- <skip />
- <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
- <skip />
- <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
- <skip />
- <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
- <skip />
- <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
- <skip />
- <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
- <skip />
- <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
- <skip />
- <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
- <skip />
+ <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"\"camera\" எனும் வகையைக் கொண்ட முன்புலச் சேவையை இயக்குதல்"</string>
+ <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"\"camera\" எனும் வகையைக் கொண்ட முன்புலச் சேவைகளைப் பயன்படுத்த ஆப்ஸை அனுமதிக்கும்"</string>
+ <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"\"connectedDevice\" எனும் வகையைக் கொண்ட முன்புலச் சேவையை இயக்குதல்"</string>
+ <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"\"connectedDevice\" எனும் வகையைக் கொண்ட முன்புலச் சேவைகளைப் பயன்படுத்த ஆப்ஸை அனுமதிக்கும்"</string>
+ <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"\"dataSync\" எனும் வகையைக் கொண்ட முன்புலச் சேவையை இயக்குதல்"</string>
+ <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"\"dataSync\" எனும் வகையைக் கொண்ட முன்புலச் சேவைகளைப் பயன்படுத்த ஆப்ஸை அனுமதிக்கும்"</string>
+ <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"\"location\" எனும் வகையைக் கொண்ட முன்புலச் சேவையை இயக்குதல்"</string>
+ <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"\"location\" எனும் வகையைக் கொண்ட முன்புலச் சேவைகளைப் பயன்படுத்த ஆப்ஸை அனுமதிக்கும்"</string>
+ <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"\"mediaPlayback\" எனும் வகையைக் கொண்ட முன்புலச் சேவையை இயக்குதல்"</string>
+ <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"\"mediaPlayback\" எனும் வகையைக் கொண்ட முன்புலச் சேவைகளைப் பயன்படுத்த ஆப்ஸை அனுமதிக்கும்"</string>
+ <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"\"mediaProjection\" எனும் வகையைக் கொண்ட முன்புலச் சேவையை இயக்குதல்"</string>
+ <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"\"mediaProjection\" எனும் வகையைக் கொண்ட முன்புலச் சேவைகளைப் பயன்படுத்த ஆப்ஸை அனுமதிக்கும்"</string>
+ <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"\"microphone\" எனும் வகையைக் கொண்ட முன்புலச் சேவையை இயக்குதல்"</string>
+ <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"\"microphone\" எனும் வகையைக் கொண்ட முன்புலச் சேவைகளைப் பயன்படுத்த ஆப்ஸை அனுமதிக்கும்"</string>
+ <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"\"phoneCall\" எனும் வகையைக் கொண்ட முன்புலச் சேவையை இயக்குதல்"</string>
+ <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"\"phoneCall\" எனும் வகையைக் கொண்ட முன்புலச் சேவைகளைப் பயன்படுத்த ஆப்ஸை அனுமதிக்கும்"</string>
+ <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"\"health\" எனும் வகையைக் கொண்ட முன்புலச் சேவையை இயக்குதல்"</string>
+ <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"\"health\" எனும் வகையைக் கொண்ட முன்புலச் சேவைகளைப் பயன்படுத்த ஆப்ஸை அனுமதிக்கும்"</string>
+ <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"\"remoteMessaging\" எனும் வகையைக் கொண்ட முன்புலச் சேவையை இயக்குதல்"</string>
+ <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"\"remoteMessaging\" எனும் வகையைக் கொண்ட முன்புலச் சேவைகளைப் பயன்படுத்த ஆப்ஸை அனுமதிக்கும்"</string>
+ <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"\"systemExempted\" எனும் வகையைக் கொண்ட முன்புலச் சேவையை இயக்குதல்"</string>
+ <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"\"systemExempted\" எனும் வகையைக் கொண்ட முன்புலச் சேவைகளைப் பயன்படுத்த ஆப்ஸை அனுமதிக்கும்"</string>
+ <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"\"specialUse\" எனும் வகையைக் கொண்ட முன்புலச் சேவையை இயக்குதல்"</string>
+ <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"\"specialUse\" எனும் வகையைக் கொண்ட முன்புலச் சேவைகளைப் பயன்படுத்த ஆப்ஸை அனுமதிக்கும்"</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"ஆப்ஸ் சேமிப்பு இடத்தை அளவிடல்"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"ஆப்ஸ், அதன் குறியீடு, தரவு, மற்றும் தற்காலிகச் சேமிப்பு அளவுகளை மீட்டெடுக்க அனுமதிக்கிறது"</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"சாதன அமைப்புகளை மாற்றுதல்"</string>
diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml
index df8d773e02da..5355f6f3fd01 100644
--- a/core/res/res/values-te/strings.xml
+++ b/core/res/res/values-te/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"వాయిస్ మెయిల్"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"కనెక్షన్ సమస్య లేదా చెల్లని MMI కోడ్."</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"ప్రస్తుత మొబైల్ నెట్‌వర్క్‌లో ఫీచర్ సపోర్ట్ చేయడం లేదు."</string>
<string name="mmiFdnError" msgid="3975490266767565852">"చర్య స్థిరమైన డయలింగ్ నంబర్‌లకు మాత్రమే పరిమితం చేయబడింది."</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"మీరు రోమింగ్‌లో ఉన్నప్పుడు మీ ఫోన్‌ నుండి కాల్ ఫార్వార్డింగ్ సెట్టింగ్‌లను మార్చలేరు."</string>
<string name="serviceEnabled" msgid="7549025003394765639">"సేవ ప్రారంభించబడింది."</string>
diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml
index b5af3ded17da..1bdaa5054070 100644
--- a/core/res/res/values-th/strings.xml
+++ b/core/res/res/values-th/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"ข้อความเสียง"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"ปัญหาการเชื่อมต่อหรือรหัส MMI ไม่ถูกต้อง"</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"ไม่รองรับฟีเจอร์"</string>
<string name="mmiFdnError" msgid="3975490266767565852">"การดำเนินการถูกจำกัดไว้ที่การจำกัดหมายเลขโทรออกเท่านั้น"</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"ไม่สามารถเปลี่ยนการตั้งค่าการโอนสายจากโทรศัพท์ในขณะที่โรมมิ่ง"</string>
<string name="serviceEnabled" msgid="7549025003394765639">"เปิดใช้งานบริการแล้ว"</string>
diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml
index 097abd6fbd19..bb17eb974037 100644
--- a/core/res/res/values-tl/strings.xml
+++ b/core/res/res/values-tl/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Voicemail"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"Problema sa koneksyon o di-wastong MMI code."</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Hindi sinusuportahan ang feature."</string>
<string name="mmiFdnError" msgid="3975490266767565852">"Pinaghihigpitan ang pagpapatakbo sa mga fixed dialing number lang."</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Hindi maaaring baguhin ang mga setting ng pagpapasa ng tawag mula sa iyong telepono habang naka-roaming ka."</string>
<string name="serviceEnabled" msgid="7549025003394765639">"Pinagana ang serbisyo."</string>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index 4bf637a865b4..3e974e308aaa 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Sesli Mesaj"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"Bağlantı sorunu veya geçersiz MMI kodu."</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Özellik desteklenmiyor."</string>
<string name="mmiFdnError" msgid="3975490266767565852">"İşlem sadece sabit arama numaralarıyla sınırlandırılmıştır."</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Dolaşımdayken telefonunuzdan çağrı yönlendirme ayarları değiştirilemiyor."</string>
<string name="serviceEnabled" msgid="7549025003394765639">"Hizmet etkindi."</string>
diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml
index 6e114dd349df..528575b2f869 100644
--- a/core/res/res/values-uk/strings.xml
+++ b/core/res/res/values-uk/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Голосова пошта"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"Пробл. підключення чи недійсний код MMI."</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Функція не підтримується."</string>
<string name="mmiFdnError" msgid="3975490266767565852">"Операція лише для номерів фіксованого набору."</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"У роумінгу на телефоні не можна змінити налаштування переадресації викликів."</string>
<string name="serviceEnabled" msgid="7549025003394765639">"Послугу ввімкнено."</string>
@@ -1193,8 +1194,8 @@
<string name="no" msgid="5122037903299899715">"Скасувати"</string>
<string name="dialog_alert_title" msgid="651856561974090712">"Увага"</string>
<string name="loading" msgid="3138021523725055037">"Завантаження..."</string>
- <string name="capital_on" msgid="2770685323900821829">"УВІМК"</string>
- <string name="capital_off" msgid="7443704171014626777">"ВИМК"</string>
+ <string name="capital_on" msgid="2770685323900821829">"УВІМКНЕНО"</string>
+ <string name="capital_off" msgid="7443704171014626777">"ВИМКНЕНО"</string>
<string name="checked" msgid="9179896827054513119">"вибрано"</string>
<string name="not_checked" msgid="7972320087569023342">"не вибрано"</string>
<string name="selected" msgid="6614607926197755875">"вибрано"</string>
diff --git a/core/res/res/values-ur/strings.xml b/core/res/res/values-ur/strings.xml
index 8afb2c8553a1..9d15d647fdb3 100644
--- a/core/res/res/values-ur/strings.xml
+++ b/core/res/res/values-ur/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"صوتی میل"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"‏کنکشن مسئلہ یا غلط MMI کوڈ۔"</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"خصوصیت تعاون یافتہ نہیں ہے۔"</string>
<string name="mmiFdnError" msgid="3975490266767565852">"آپریشن صرف متعین ڈائلنگ نمبرز تک محدود ہے۔"</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"جب آپ رومنگ پر ہوں تو اپنے فون سے کال فارورڈنگ کی ترتیبات تبدیل نہیں کی جا سکتیں۔"</string>
<string name="serviceEnabled" msgid="7549025003394765639">"سروس فعال کی گئی۔"</string>
diff --git a/core/res/res/values-uz/strings.xml b/core/res/res/values-uz/strings.xml
index 984981db2140..f987eaef976c 100644
--- a/core/res/res/values-uz/strings.xml
+++ b/core/res/res/values-uz/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Ovozli pochta"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"Tarmoqda xato yoki MMI kod noto‘g‘ri."</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Ishlamaydigan funksiya."</string>
<string name="mmiFdnError" msgid="3975490266767565852">"Bu amal faqat ruxsat etilgan raqamlar uchun mavjud."</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Rouming vaqtida telefondagi chaqiruvni boshqa raqamga uzatish sozlamalarini o‘zgartirib bo‘lmadi."</string>
<string name="serviceEnabled" msgid="7549025003394765639">"Xizmat yoqildi."</string>
diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml
index 4e2dcebd1c63..af1ae05f8eee 100644
--- a/core/res/res/values-vi/strings.xml
+++ b/core/res/res/values-vi/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Thư thoại"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"Sự cố kết nối hoặc mã MMI không hợp lệ."</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Tính năng không được hỗ trợ."</string>
<string name="mmiFdnError" msgid="3975490266767565852">"Chỉ hạn chế thao tác đối với số quay số định sẵn."</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Không thể thay đổi cài đặt chuyển tiếp cuộc gọi từ điện thoại của bạn khi bạn đang chuyển vùng."</string>
<string name="serviceEnabled" msgid="7549025003394765639">"Dịch vụ đã được bật."</string>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index d4bfe15724b8..73f5b03480e0 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"语音信箱"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"出现连接问题或 MMI 码无效。"</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"不支持此功能。"</string>
<string name="mmiFdnError" msgid="3975490266767565852">"只能对固定拨号号码执行此类操作。"</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"漫游时无法通过您的手机来更改来电转接设置。"</string>
<string name="serviceEnabled" msgid="7549025003394765639">"已启用服务。"</string>
diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml
index 8344683e8590..4993c8934e72 100644
--- a/core/res/res/values-zh-rHK/strings.xml
+++ b/core/res/res/values-zh-rHK/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"留言信箱"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"連線發生問題或 MMI 碼無效。"</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"不支援的功能。"</string>
<string name="mmiFdnError" msgid="3975490266767565852">"僅限對固定撥號號碼執行這項運作。"</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"使用漫遊服務時,不可從手機變更來電轉駁設定。"</string>
<string name="serviceEnabled" msgid="7549025003394765639">"服務已啟用。"</string>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index 0f48935323cd..7dde34373511 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"語音留言"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"連線發生問題或錯誤的 MMI 碼。"</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"不支援的功能。"</string>
<string name="mmiFdnError" msgid="3975490266767565852">"僅限對固定撥號號碼執行此作業。"</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"漫遊時無法透過你的手機變更來電轉接設定。"</string>
<string name="serviceEnabled" msgid="7549025003394765639">"服務已啟用。"</string>
diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml
index cd8fcc764d39..86f869de5771 100644
--- a/core/res/res/values-zu/strings.xml
+++ b/core/res/res/values-zu/strings.xml
@@ -28,6 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Ivoyisimeyili"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"Inkinga yoxhumano noma ikhadi ye-MMI engalungile."</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Isakhi asisekelwa."</string>
<string name="mmiFdnError" msgid="3975490266767565852">"Umsebenzi uvinjelwe ekudayeleni izinombolo ezingaguquki kuphela."</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Ayikwazi ukushintsha izilungiselelo zokudluliselwa kwekholi kusuka efonini yakho ngenkathi uzula."</string>
<string name="serviceEnabled" msgid="7549025003394765639">"Isevisi ivaliwe."</string>
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index eb7034437423..d4644c59fa52 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -294,6 +294,9 @@
<!-- Additional flag from base permission type: this permission can be automatically
granted to the system app predictor -->
<flag name="appPredictor" value="0x200000" />
+ <!-- Additional flag from base permission type: this permission can also be granted if the
+ requesting application is included in the mainline module}. -->
+ <flag name="module" value="0x400000" />
<!-- Additional flag from base permission type: this permission can be automatically
granted to the system companion device manager service -->
<flag name="companion" value="0x800000" />
@@ -1709,6 +1712,13 @@
for more details.
-->
<flag name="shortService" value="0x800" />
+ <!-- The file management use case which manages files/directories, often involving file I/O
+ across the file system.
+ <p>Requires the app to hold the permission
+ {@link android.Manifest.permission#FOREGROUND_SERVICE_FILE_MANAGEMENT} in order to use
+ this type.
+ -->
+ <flag name="fileManagement" value="0x1000" />
<!-- Use cases that can't be categorized into any other foreground service types, but also
can't use @link android.app.job.JobInfo.Builder} APIs.
See {@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_SPECIAL_USE} for the
@@ -3165,20 +3175,20 @@
<attr name="canDisplayOnRemoteDevices" format="boolean"/>
<attr name="allowUntrustedActivityEmbedding" />
<attr name="knownActivityEmbeddingCerts" />
- <!-- Specifies the category of the target display the activity is expected to run on. Upon
- creation, a virtual display can specify which display categories it supports and one of
- the category must be present in the activity's manifest to allow this activity to run.
- The default value is {@code null}, which indicates the activity does not belong to a
- restricted display category and thus can only run on a display that didn't specify any
- display categories. Each activity can only specify one category it targets to but a
- virtual display can accommodate multiple restricted categories.
+ <!-- Specifies the required display category of the activity. Upon creation, a display can
+ specify which display categories it supports and one of the categories must be present
+ in the {@code <activity>} element to allow this activity to run. The default value is
+ {@code null}, which indicates the activity does not have a required display category
+ and thus can only run on a display that didn't specify any display categories. Each
+ activity can only specify one required category but a display can accommodate multiple
+ display categories.
<p> This field should be formatted as a Java-language-style free form string(for
example, com.google.automotive_entertainment), which may contain uppercase or lowercase
letters ('A' through 'Z'), numbers, and underscores ('_') but may only start with
letters.
-->
- <attr name="targetDisplayCategory" format="string"/>
+ <attr name="requiredDisplayCategory" format="string"/>
</declare-styleable>
<!-- The <code>activity-alias</code> tag declares a new
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 9a585a194ed1..1d18d4195edf 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2507,6 +2507,10 @@
states. -->
<bool name="config_dozeAlwaysOnDisplayAvailable">false</bool>
+ <!-- Control whether the pickup gesture is enabled by default. This value will be used
+ during initialization when the setting is still null. -->
+ <bool name="config_dozePickupGestureEnabled">true</bool>
+
<!-- Control whether the always on display mode is enabled by default. This value will be used
during initialization when the setting is still null. -->
<bool name="config_dozeAlwaysOnEnabled">true</bool>
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index f2a16d355ed7..d40adf53abd1 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -118,6 +118,11 @@
<bool name="config_using_subscription_manager_service">false</bool>
<java-symbol type="bool" name="config_using_subscription_manager_service" />
+ <!-- Whether asynchronously update the subscription database or not. Async mode increases
+ the performance, but sync mode reduces the chance of database/cache out-of-sync. -->
+ <bool name="config_subscription_database_async_update">true</bool>
+ <java-symbol type="bool" name="config_subscription_database_async_update" />
+
<!-- Boolean indicating whether the emergency numbers for a country, sourced from modem/config,
should be ignored if that country is 'locked' (i.e. ignore_modem_config set to true) in
Android Emergency DB. If this value is true, emergency numbers for a country, sourced from
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index bc5878a0b2ed..a9bec7a9fe28 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -116,7 +116,7 @@
<public name="handwritingBoundsOffsetBottom" />
<public name="accessibilityDataPrivate" />
<public name="enableTextStylingShortcuts" />
- <public name="targetDisplayCategory"/>
+ <public name="requiredDisplayCategory"/>
<public name="maxConcurrentSessionsCount" />
</staging-public-group>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index d0372eae2ae7..18a5c72b0892 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1133,6 +1133,16 @@
<string name="permdesc_useDataInBackground">This app can use data in the background. This may increase data usage.</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_schedule_exact_alarm">Schedule precisely timed actions</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_schedule_exact_alarm">This app can schedule work to happen at a desired time in the future. This also means that the app can run when you\u2019re not actively using the device.</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_use_exact_alarm">Schedule alarms or event reminders</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_use_exact_alarm">This app can schedule actions like alarms and reminders to notify you at a desired time in the future.</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_persistentActivity">make app always run</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permdesc_persistentActivity" product="tablet">Allows the app to make parts of itself persistent in memory. This can limit memory available to other apps slowing down the tablet.</string>
@@ -1201,6 +1211,11 @@
<string name="permdesc_foregroundServiceSystemExempted">Allows the app to make use of foreground services with the type \"systemExempted\"</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_foregroundServiceFileManagement">run foreground service with the type \"fileManagement\"</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_foregroundServiceFileManagement">Allows the app to make use of foreground services with the type \"fileManagement\"</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_foregroundServiceSpecialUse">run foreground service with the type \"specialUse\"</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permdesc_foregroundServiceSpecialUse">Allows the app to make use of foreground services with the type \"specialUse\"</string>
@@ -6402,4 +6417,22 @@ ul.</string>
<!-- Display content to tell the user the sim card name and number-->
<string name="default_card_name">CARD <xliff:g id="cardNumber" example="1">%d</xliff:g></string>
+
+ <!-- Strings for CompanionDeviceManager -->
+ <!-- Title of Watch profile permission, which allows a companion app to manage watches. [CHAR LIMIT=NONE] -->
+ <string name="permlab_companionProfileWatch">Companion Watch profile permission to manage watches</string>
+ <!-- Description of Watch profile permission, which allows a companion app to manage watches. [CHAR LIMIT=NONE] -->
+ <string name="permdesc_companionProfileWatch">Allows a companion app to manage watches.</string>
+ <!-- Title of observing device presence permission, which allows a companion app to observe the presence of the associated devices. [CHAR LIMIT=NONE] -->
+ <string name="permlab_observeCompanionDevicePresence">Observe companion device presence</string>
+ <!-- Description of observing device presence permission, which allows a companion app to observe the presence of the associated devices. [CHAR LIMIT=NONE] -->
+ <string name="permdesc_observeCompanionDevicePresence">Allows a companion app to observe companion device presence when the devices are nearby or far-away.</string>
+ <!-- Title of delivering companion messages permission, which allows a companion app to deliver messages to other devices. [CHAR LIMIT=NONE] -->
+ <string name="permlab_deliverCompanionMessages">Deliver companion messages</string>
+ <!-- Description of delivering companion messages permission, which allows a companion app to deliver messages to other devices. [CHAR LIMIT=NONE] -->
+ <string name="permdesc_deliverCompanionMessages">Allows a companion app to deliver companion messages to other devices.</string>
+ <!-- Title of start foreground services from background permission [CHAR LIMIT=NONE] -->
+ <string name="permlab_startForegroundServicesFromBackground">Start foreground services from background</string>
+ <!-- Description of start foreground services from background permission [CHAR LIMIT=NONE] -->
+ <string name="permdesc_startForegroundServicesFromBackground">Allows a companion app to start foreground services from background.</string>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index ace7e4c31b4a..54be56805b01 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3826,6 +3826,7 @@
<java-symbol type="string" name="config_cameraShutterSound" />
<java-symbol type="integer" name="config_autoGroupAtCount" />
<java-symbol type="bool" name="config_dozeAlwaysOnDisplayAvailable" />
+ <java-symbol type="bool" name="config_dozePickupGestureEnabled" />
<java-symbol type="bool" name="config_dozeAlwaysOnEnabled" />
<java-symbol type="bool" name="config_dozeSupportsAodWallpaper" />
<java-symbol type="bool" name="config_displayBlanksAfterDoze" />
diff --git a/core/res/res/xml/bookmarks.xml b/core/res/res/xml/bookmarks.xml
index 454f4562a239..3087aaaefd41 100644
--- a/core/res/res/xml/bookmarks.xml
+++ b/core/res/res/xml/bookmarks.xml
@@ -19,23 +19,20 @@
Bookmarks for vendor apps should be added to a bookmarks resource overlay; not here.
Typical shortcuts (not necessarily defined here):
- 'a': Calculator
'b': Browser
'c': Contacts
'e': Email
'g': GMail
- 'l': Calendar
+ 'k': Calendar
'm': Maps
'p': Music
's': SMS
't': Talk
+ 'u': Calculator
'y': YouTube
-->
<bookmarks>
<bookmark
- category="android.intent.category.APP_CALCULATOR"
- shortcut="a" />
- <bookmark
category="android.intent.category.APP_BROWSER"
shortcut="b" />
<bookmark
@@ -46,7 +43,7 @@
shortcut="e" />
<bookmark
category="android.intent.category.APP_CALENDAR"
- shortcut="l" />
+ shortcut="k" />
<bookmark
category="android.intent.category.APP_MAPS"
shortcut="m" />
@@ -56,4 +53,7 @@
<bookmark
category="android.intent.category.APP_MESSAGING"
shortcut="s" />
+ <bookmark
+ category="android.intent.category.APP_CALCULATOR"
+ shortcut="u" />
</bookmarks>
diff --git a/core/tests/GameManagerTests/src/android/app/GameModeConfigurationTest.java b/core/tests/GameManagerTests/src/android/app/GameModeConfigurationTest.java
index 7462bcf67480..b3e74d3f553a 100644
--- a/core/tests/GameManagerTests/src/android/app/GameModeConfigurationTest.java
+++ b/core/tests/GameManagerTests/src/android/app/GameModeConfigurationTest.java
@@ -77,10 +77,10 @@ public class GameModeConfigurationTest {
}
@Test
- public void testToBuilder() {
+ public void testBuilderConstructor() {
GameModeConfiguration config = new GameModeConfiguration
.Builder().setFpsOverride(40).setScalingFactor(0.5f).build();
- GameModeConfiguration newConfig = config.toBuilder().build();
+ GameModeConfiguration newConfig = new GameModeConfiguration.Builder(config).build();
assertEquals(config, newConfig);
}
diff --git a/core/tests/GameManagerTests/src/android/app/GameModeInfoTest.java b/core/tests/GameManagerTests/src/android/app/GameModeInfoTest.java
index ecd9b6b8143a..5fa60849dbfc 100644
--- a/core/tests/GameManagerTests/src/android/app/GameModeInfoTest.java
+++ b/core/tests/GameManagerTests/src/android/app/GameModeInfoTest.java
@@ -43,7 +43,7 @@ public class GameModeInfoTest {
int[] availableGameModes =
new int[]{GameManager.GAME_MODE_STANDARD, GameManager.GAME_MODE_PERFORMANCE,
GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_CUSTOM};
- int[] optedInGameModes = new int[]{GameManager.GAME_MODE_PERFORMANCE};
+ int[] overriddenGameModes = new int[]{GameManager.GAME_MODE_PERFORMANCE};
GameModeConfiguration batteryConfig = new GameModeConfiguration
.Builder().setFpsOverride(40).setScalingFactor(0.5f).build();
GameModeConfiguration performanceConfig = new GameModeConfiguration
@@ -51,7 +51,7 @@ public class GameModeInfoTest {
GameModeInfo gameModeInfo = new GameModeInfo.Builder()
.setActiveGameMode(activeGameMode)
.setAvailableGameModes(availableGameModes)
- .setOptedInGameModes(optedInGameModes)
+ .setOverriddenGameModes(overriddenGameModes)
.setDownscalingAllowed(true)
.setFpsOverrideAllowed(false)
.setGameModeConfiguration(GameManager.GAME_MODE_BATTERY, batteryConfig)
@@ -59,7 +59,7 @@ public class GameModeInfoTest {
.build();
assertArrayEquals(availableGameModes, gameModeInfo.getAvailableGameModes());
- assertArrayEquals(optedInGameModes, gameModeInfo.getOptedInGameModes());
+ assertArrayEquals(overriddenGameModes, gameModeInfo.getOverriddenGameModes());
assertEquals(activeGameMode, gameModeInfo.getActiveGameMode());
assertTrue(gameModeInfo.isDownscalingAllowed());
assertFalse(gameModeInfo.isFpsOverrideAllowed());
@@ -75,8 +75,8 @@ public class GameModeInfoTest {
assertEquals(gameModeInfo.getActiveGameMode(), newGameModeInfo.getActiveGameMode());
assertArrayEquals(gameModeInfo.getAvailableGameModes(),
newGameModeInfo.getAvailableGameModes());
- assertArrayEquals(gameModeInfo.getOptedInGameModes(),
- newGameModeInfo.getOptedInGameModes());
+ assertArrayEquals(gameModeInfo.getOverriddenGameModes(),
+ newGameModeInfo.getOverriddenGameModes());
assertTrue(newGameModeInfo.isDownscalingAllowed());
assertFalse(newGameModeInfo.isFpsOverrideAllowed());
assertEquals(performanceConfig,
diff --git a/core/tests/coretests/src/android/app/backup/BackupManagerTest.java b/core/tests/coretests/src/android/app/backup/BackupManagerTest.java
new file mode 100644
index 000000000000..cbf167c49f95
--- /dev/null
+++ b/core/tests/coretests/src/android/app/backup/BackupManagerTest.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.backup;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import android.app.backup.BackupAnnotations.BackupDestination;
+import android.app.backup.BackupAnnotations.OperationType;
+import android.content.Context;
+import android.os.ParcelFileDescriptor;
+import android.os.UserHandle;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.function.ThrowingRunnable;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.IOException;
+
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class BackupManagerTest {
+ private BackupManager mBackupManager;
+
+ @Mock
+ Context mContext;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mBackupManager = new BackupManager(mContext);
+ }
+
+ @Test
+ public void testGetBackupRestoreEventLogger_returnsBackupLoggerForBackup() {
+ BackupAgent agent = getTestAgent();
+ agent.onCreate(UserHandle.SYSTEM, BackupDestination.CLOUD,
+ OperationType.BACKUP);
+
+ BackupRestoreEventLogger logger = mBackupManager.getBackupRestoreEventLogger(agent);
+
+ assertThat(logger.getOperationType()).isEqualTo(OperationType.BACKUP);
+ }
+
+ @Test
+ public void testGetBackupRestoreEventLogger_returnsRestoreLoggerForRestore() {
+ BackupAgent agent = getTestAgent();
+ agent.onCreate(UserHandle.SYSTEM, BackupDestination.CLOUD,
+ OperationType.RESTORE);
+
+ BackupRestoreEventLogger logger = mBackupManager.getBackupRestoreEventLogger(agent);
+
+ assertThat(logger.getOperationType()).isEqualTo(OperationType.RESTORE);
+ }
+
+ @Test
+ public void testGetBackupRestoreEventLogger_uninitialisedAgent_throwsException() {
+ BackupAgent agent = getTestAgent();
+
+ assertThrows(IllegalStateException.class,
+ () -> mBackupManager.getBackupRestoreEventLogger(agent));
+ }
+
+ private static BackupAgent getTestAgent() {
+ return new BackupAgent() {
+ @Override
+ public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
+ ParcelFileDescriptor newState) throws IOException {
+
+ }
+
+ @Override
+ public void onRestore(BackupDataInput data, int appVersionCode,
+ ParcelFileDescriptor newState) throws IOException {
+
+ }
+ };
+ }
+
+}
diff --git a/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java b/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java
index d1d14f6fbcb4..44923b60fbb5 100644
--- a/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java
+++ b/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java
@@ -117,7 +117,8 @@ public class PerformanceHintManagerTest {
public void testSendHint() {
Session s = createSession();
assumeNotNull(s);
- s.sendHint(Session.CPU_LOAD_UP);
+ s.sendHint(Session.CPU_LOAD_RESET);
+ // ensure we can also send within the rate limit without exception
s.sendHint(Session.CPU_LOAD_RESET);
}
diff --git a/core/tests/coretests/src/android/provider/DeviceConfigTest.java b/core/tests/coretests/src/android/provider/DeviceConfigTest.java
index 352c6a736af1..aa1853f50028 100644
--- a/core/tests/coretests/src/android/provider/DeviceConfigTest.java
+++ b/core/tests/coretests/src/android/provider/DeviceConfigTest.java
@@ -857,7 +857,10 @@ public class DeviceConfigTest {
ContentResolver resolver = InstrumentationRegistry.getContext().getContentResolver();
String compositeName = namespace + "/" + key;
Bundle result = resolver.call(
- DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, compositeName, null);
+ Settings.Config.CONTENT_URI,
+ Settings.CALL_METHOD_DELETE_CONFIG,
+ compositeName,
+ null);
assertThat(result).isNotNull();
return compositeName.equals(result.getString(Settings.NameValueTable.VALUE));
}
diff --git a/core/tests/coretests/src/android/provider/NameValueCacheTest.java b/core/tests/coretests/src/android/provider/NameValueCacheTest.java
index ee0b127c696b..2e31bb581fbc 100644
--- a/core/tests/coretests/src/android/provider/NameValueCacheTest.java
+++ b/core/tests/coretests/src/android/provider/NameValueCacheTest.java
@@ -76,7 +76,7 @@ public class NameValueCacheTest {
when(mMockContentProvider.getIContentProvider()).thenReturn(mMockIContentProvider);
mMockContentResolver = new MockContentResolver(InstrumentationRegistry
.getInstrumentation().getContext());
- mMockContentResolver.addProvider(DeviceConfig.CONTENT_URI.getAuthority(),
+ mMockContentResolver.addProvider(Settings.Config.CONTENT_URI.getAuthority(),
mMockContentProvider);
mCacheGenerationStore = new MemoryIntArray(1);
mStorage = new HashMap<>();
@@ -84,7 +84,7 @@ public class NameValueCacheTest {
// Stores keyValues for a given prefix and increments the generation. (Note that this
// increments the generation no matter what, it doesn't pay attention to if anything
// actually changed).
- when(mMockIContentProvider.call(any(), eq(DeviceConfig.CONTENT_URI.getAuthority()),
+ when(mMockIContentProvider.call(any(), eq(Settings.Config.CONTENT_URI.getAuthority()),
eq(Settings.CALL_METHOD_SET_ALL_CONFIG),
any(), any(Bundle.class))).thenAnswer(invocationOnMock -> {
Bundle incomingBundle = invocationOnMock.getArgument(4);
@@ -104,7 +104,7 @@ public class NameValueCacheTest {
// Returns the keyValues corresponding to a namespace, or an empty map if the namespace
// doesn't have anything stored for it. Returns the generation key if the caller asked
// for one.
- when(mMockIContentProvider.call(any(), eq(DeviceConfig.CONTENT_URI.getAuthority()),
+ when(mMockIContentProvider.call(any(), eq(Settings.Config.CONTENT_URI.getAuthority()),
eq(Settings.CALL_METHOD_LIST_CONFIG),
any(), any(Bundle.class))).thenAnswer(invocationOnMock -> {
Bundle incomingBundle = invocationOnMock.getArgument(4);
diff --git a/core/tests/coretests/src/android/provider/SettingsProviderTest.java b/core/tests/coretests/src/android/provider/SettingsProviderTest.java
index 4adbc9134698..133177935984 100644
--- a/core/tests/coretests/src/android/provider/SettingsProviderTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsProviderTest.java
@@ -345,27 +345,33 @@ public class SettingsProviderTest extends AndroidTestCase {
try {
// value is empty
Bundle results =
- r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_GET_CONFIG, name, null);
+ r.call(Settings.Config.CONTENT_URI,
+ Settings.CALL_METHOD_GET_CONFIG, name, null);
assertNull(results.get(Settings.NameValueTable.VALUE));
// save value
- results = r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, name, args);
+ results = r.call(Settings.Config.CONTENT_URI,
+ Settings.CALL_METHOD_PUT_CONFIG, name, args);
assertNull(results);
// value is no longer empty
- results = r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_GET_CONFIG, name, null);
+ results = r.call(Settings.Config.CONTENT_URI,
+ Settings.CALL_METHOD_GET_CONFIG, name, null);
assertEquals(value, results.get(Settings.NameValueTable.VALUE));
// save new value
args.putString(Settings.NameValueTable.VALUE, newValue);
- r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, name, args);
+ r.call(Settings.Config.CONTENT_URI,
+ Settings.CALL_METHOD_PUT_CONFIG, name, args);
// new value is returned
- results = r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_GET_CONFIG, name, null);
+ results = r.call(Settings.Config.CONTENT_URI,
+ Settings.CALL_METHOD_GET_CONFIG, name, null);
assertEquals(newValue, results.get(Settings.NameValueTable.VALUE));
} finally {
// clean up
- r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, name, null);
+ r.call(Settings.Config.CONTENT_URI,
+ Settings.CALL_METHOD_DELETE_CONFIG, name, null);
}
}
@@ -379,23 +385,25 @@ public class SettingsProviderTest extends AndroidTestCase {
try {
// save value
- r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, name, args);
+ r.call(Settings.Config.CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, name, args);
// get value
Bundle results =
- r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_GET_CONFIG, name, null);
+ r.call(Settings.Config.CONTENT_URI,
+ Settings.CALL_METHOD_GET_CONFIG, name, null);
assertEquals(value, results.get(Settings.NameValueTable.VALUE));
// delete value
- results = r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, name,
+ results = r.call(Settings.Config.CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, name,
null);
// value is empty now
- results = r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_GET_CONFIG, name, null);
+ results = r.call(Settings.Config.CONTENT_URI,
+ Settings.CALL_METHOD_GET_CONFIG, name, null);
assertNull(results.get(Settings.NameValueTable.VALUE));
} finally {
// clean up
- r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, name, null);
+ r.call(Settings.Config.CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, name, null);
}
}
@@ -413,12 +421,12 @@ public class SettingsProviderTest extends AndroidTestCase {
try {
// save both values
- r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, name, args);
+ r.call(Settings.Config.CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, name, args);
args.putString(Settings.NameValueTable.VALUE, newValue);
- r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, newName, args);
+ r.call(Settings.Config.CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, newName, args);
// list all values
- Bundle result = r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_LIST_CONFIG,
+ Bundle result = r.call(Settings.Config.CONTENT_URI, Settings.CALL_METHOD_LIST_CONFIG,
null, null);
Map<String, String> keyValueMap =
(HashMap) result.getSerializable(Settings.NameValueTable.VALUE);
@@ -428,14 +436,15 @@ public class SettingsProviderTest extends AndroidTestCase {
// list values for prefix
args.putString(Settings.CALL_METHOD_PREFIX_KEY, prefix);
- result = r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_LIST_CONFIG, null, args);
+ result = r.call(Settings.Config.CONTENT_URI,
+ Settings.CALL_METHOD_LIST_CONFIG, null, args);
keyValueMap = (HashMap) result.getSerializable(Settings.NameValueTable.VALUE);
assertThat(keyValueMap, aMapWithSize(1));
assertEquals(value, keyValueMap.get(name));
} finally {
// clean up
- r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, name, null);
- r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, newName, null);
+ r.call(Settings.Config.CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, name, null);
+ r.call(Settings.Config.CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, newName, null);
}
}
}
diff --git a/core/tests/coretests/src/android/view/InsetsVisibilitiesTest.java b/core/tests/coretests/src/android/view/InsetsVisibilitiesTest.java
deleted file mode 100644
index 5664e0b0aa0f..000000000000
--- a/core/tests/coretests/src/android/view/InsetsVisibilitiesTest.java
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view;
-
-import static android.view.InsetsState.FIRST_TYPE;
-import static android.view.InsetsState.LAST_TYPE;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertTrue;
-
-import android.platform.test.annotations.Presubmit;
-import android.view.InsetsState.InternalInsetsType;
-
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Tests for {@link InsetsVisibilities}.
- *
- * <p>Build/Install/Run:
- * atest FrameworksCoreTests:InsetsVisibilities
- *
- * <p>This test class is a part of Window Manager Service tests and specified in
- * {@link com.android.server.wm.test.filters.FrameworksTestsFilter}.
- */
-@Presubmit
-@RunWith(AndroidJUnit4.class)
-public class InsetsVisibilitiesTest {
-
- @Test
- public void testEquals() {
- final InsetsVisibilities v1 = new InsetsVisibilities();
- final InsetsVisibilities v2 = new InsetsVisibilities();
- final InsetsVisibilities v3 = new InsetsVisibilities();
- assertEquals(v1, v2);
- assertEquals(v1, v3);
-
- for (@InternalInsetsType int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
- v1.setVisibility(type, false);
- v2.setVisibility(type, false);
- }
- assertEquals(v1, v2);
- assertNotEquals(v1, v3);
-
- for (@InternalInsetsType int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
- v1.setVisibility(type, true);
- v2.setVisibility(type, true);
- }
- assertEquals(v1, v2);
- assertNotEquals(v1, v3);
- }
-
- @Test
- public void testSet() {
- for (@InternalInsetsType int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
- final InsetsVisibilities v1 = new InsetsVisibilities();
- final InsetsVisibilities v2 = new InsetsVisibilities();
-
- v1.setVisibility(type, true);
- assertNotEquals(v1, v2);
-
- v2.set(v1);
- assertEquals(v1, v2);
-
- v2.setVisibility(type, false);
- assertNotEquals(v1, v2);
-
- v1.set(v2);
- assertEquals(v1, v2);
- }
- }
-
- @Test
- public void testCopyConstructor() {
- for (@InternalInsetsType int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
- final InsetsVisibilities v1 = new InsetsVisibilities();
- v1.setVisibility(type, true);
- final InsetsVisibilities v2 = new InsetsVisibilities(v1);
- assertEquals(v1, v2);
-
- v2.setVisibility(type, false);
- assertNotEquals(v1, v2);
- }
- }
-
- @Test
- public void testGetterAndSetter() {
- final InsetsVisibilities v1 = new InsetsVisibilities();
- final InsetsVisibilities v2 = new InsetsVisibilities();
-
- for (@InternalInsetsType int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
- assertEquals(InsetsState.getDefaultVisibility(type), v1.getVisibility(type));
- }
-
- for (@InternalInsetsType int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
- v1.setVisibility(type, true);
- assertTrue(v1.getVisibility(type));
-
- v2.setVisibility(type, false);
- assertFalse(v2.getVisibility(type));
- }
- }
-}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/TaskSnapshotWindowTest.java b/core/tests/coretests/src/android/window/SnapshotDrawerUtilsTest.java
index 004df2a29e58..281d677dccb0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/TaskSnapshotWindowTest.java
+++ b/core/tests/coretests/src/android/window/SnapshotDrawerUtilsTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2017 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,22 +14,20 @@
* limitations under the License.
*/
-package com.android.wm.shell.startingsurface;
+package android.window;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
-
import static org.junit.Assert.assertEquals;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.app.ActivityManager.TaskDescription;
import android.content.ComponentName;
@@ -42,33 +40,28 @@ import android.hardware.HardwareBuffer;
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.WindowInsets;
-import android.window.TaskSnapshot;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import com.android.wm.shell.ShellTestCase;
-import com.android.wm.shell.TestShellExecutor;
-
import org.junit.Test;
import org.junit.runner.RunWith;
/**
- * Test class for {@link TaskSnapshotWindow}.
- *
+ * Test class for {@link SnapshotDrawerUtils}.
*/
@SmallTest
@RunWith(AndroidJUnit4.class)
-public class TaskSnapshotWindowTest extends ShellTestCase {
+public class SnapshotDrawerUtilsTest {
- private TaskSnapshotWindow mWindow;
+ private SnapshotDrawerUtils.SnapshotSurface mSnapshotSurface;
private void setupSurface(int width, int height) {
- setupSurface(width, height, new Rect(), 0, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
+ setupSurface(width, height, new Rect(), FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
new Rect(0, 0, width, height));
}
- private void setupSurface(int width, int height, Rect contentInsets, int sysuiVis,
+ private void setupSurface(int width, int height, Rect contentInsets,
int windowFlags, Rect taskBounds) {
// Previously when constructing TaskSnapshots for this test, scale was 1.0f, so to mimic
// this behavior set the taskSize to be the same as the taskBounds width and height. The
@@ -80,12 +73,13 @@ public class TaskSnapshotWindowTest extends ShellTestCase {
Point taskSize = new Point(taskBounds.width(), taskBounds.height());
final TaskSnapshot snapshot = createTaskSnapshot(width, height, taskSize, contentInsets);
- mWindow = new TaskSnapshotWindow(new SurfaceControl(), snapshot, "Test",
- createTaskDescription(Color.WHITE, Color.RED, Color.BLUE),
- 0 /* appearance */, windowFlags /* windowFlags */, 0 /* privateWindowFlags */,
- taskBounds, ORIENTATION_PORTRAIT, ACTIVITY_TYPE_STANDARD,
- WindowInsets.Type.defaultVisible(), null /* clearWindow */,
- new TestShellExecutor());
+ TaskDescription taskDescription = createTaskDescription(Color.WHITE,
+ Color.RED, Color.BLUE);
+
+ mSnapshotSurface = new SnapshotDrawerUtils.SnapshotSurface(
+ new SurfaceControl(), snapshot, "Test", taskBounds);
+ mSnapshotSurface.initiateSystemBarPainter(windowFlags, 0, 0,
+ taskDescription, WindowInsets.Type.defaultVisible());
}
private TaskSnapshot createTaskSnapshot(int width, int height, Point taskSize,
@@ -101,8 +95,8 @@ public class TaskSnapshotWindowTest extends ShellTestCase {
0 /* systemUiVisibility */, false /* isTranslucent */, false /* hasImeSurface */);
}
- private static TaskDescription createTaskDescription(int background, int statusBar,
- int navigationBar) {
+ private static TaskDescription createTaskDescription(int background,
+ int statusBar, int navigationBar) {
final TaskDescription td = new TaskDescription();
td.setBackgroundColor(background);
td.setStatusBarColor(statusBar);
@@ -116,7 +110,7 @@ public class TaskSnapshotWindowTest extends ShellTestCase {
final Canvas mockCanvas = mock(Canvas.class);
when(mockCanvas.getWidth()).thenReturn(200);
when(mockCanvas.getHeight()).thenReturn(100);
- mWindow.drawBackgroundAndBars(mockCanvas, new Rect(0, 0, 100, 200));
+ mSnapshotSurface.drawBackgroundAndBars(mockCanvas, new Rect(0, 0, 100, 200));
verify(mockCanvas).drawRect(eq(100.0f), eq(0.0f), eq(200.0f), eq(100.0f), any());
}
@@ -126,7 +120,7 @@ public class TaskSnapshotWindowTest extends ShellTestCase {
final Canvas mockCanvas = mock(Canvas.class);
when(mockCanvas.getWidth()).thenReturn(100);
when(mockCanvas.getHeight()).thenReturn(200);
- mWindow.drawBackgroundAndBars(mockCanvas, new Rect(0, 0, 200, 100));
+ mSnapshotSurface.drawBackgroundAndBars(mockCanvas, new Rect(0, 0, 200, 100));
verify(mockCanvas).drawRect(eq(0.0f), eq(100.0f), eq(100.0f), eq(200.0f), any());
}
@@ -136,7 +130,7 @@ public class TaskSnapshotWindowTest extends ShellTestCase {
final Canvas mockCanvas = mock(Canvas.class);
when(mockCanvas.getWidth()).thenReturn(200);
when(mockCanvas.getHeight()).thenReturn(200);
- mWindow.drawBackgroundAndBars(mockCanvas, new Rect(0, 0, 100, 100));
+ mSnapshotSurface.drawBackgroundAndBars(mockCanvas, new Rect(0, 0, 100, 100));
verify(mockCanvas).drawRect(eq(100.0f), eq(0.0f), eq(200.0f), eq(100.0f), any());
verify(mockCanvas).drawRect(eq(0.0f), eq(100.0f), eq(200.0f), eq(200.0f), any());
}
@@ -147,7 +141,7 @@ public class TaskSnapshotWindowTest extends ShellTestCase {
final Canvas mockCanvas = mock(Canvas.class);
when(mockCanvas.getWidth()).thenReturn(100);
when(mockCanvas.getHeight()).thenReturn(100);
- mWindow.drawBackgroundAndBars(mockCanvas, new Rect(0, 0, 100, 100));
+ mSnapshotSurface.drawBackgroundAndBars(mockCanvas, new Rect(0, 0, 100, 100));
verify(mockCanvas, never()).drawRect(anyInt(), anyInt(), anyInt(), anyInt(), any());
}
@@ -157,76 +151,76 @@ public class TaskSnapshotWindowTest extends ShellTestCase {
final Canvas mockCanvas = mock(Canvas.class);
when(mockCanvas.getWidth()).thenReturn(100);
when(mockCanvas.getHeight()).thenReturn(100);
- mWindow.drawBackgroundAndBars(mockCanvas, new Rect(0, 0, 200, 200));
+ mSnapshotSurface.drawBackgroundAndBars(mockCanvas, new Rect(0, 0, 200, 200));
verify(mockCanvas, never()).drawRect(anyInt(), anyInt(), anyInt(), anyInt(), any());
}
@Test
public void testCalculateSnapshotCrop() {
- setupSurface(100, 100, new Rect(0, 10, 0, 10), 0, 0, new Rect(0, 0, 100, 100));
- assertEquals(new Rect(0, 0, 100, 90), mWindow.calculateSnapshotCrop());
+ setupSurface(100, 100, new Rect(0, 10, 0, 10), 0, new Rect(0, 0, 100, 100));
+ assertEquals(new Rect(0, 0, 100, 90), mSnapshotSurface.calculateSnapshotCrop());
}
@Test
public void testCalculateSnapshotCrop_taskNotOnTop() {
- setupSurface(100, 100, new Rect(0, 10, 0, 10), 0, 0, new Rect(0, 50, 100, 150));
- assertEquals(new Rect(0, 10, 100, 90), mWindow.calculateSnapshotCrop());
+ setupSurface(100, 100, new Rect(0, 10, 0, 10), 0, new Rect(0, 50, 100, 150));
+ assertEquals(new Rect(0, 10, 100, 90), mSnapshotSurface.calculateSnapshotCrop());
}
@Test
public void testCalculateSnapshotCrop_navBarLeft() {
- setupSurface(100, 100, new Rect(10, 10, 0, 0), 0, 0, new Rect(0, 0, 100, 100));
- assertEquals(new Rect(10, 0, 100, 100), mWindow.calculateSnapshotCrop());
+ setupSurface(100, 100, new Rect(10, 10, 0, 0), 0, new Rect(0, 0, 100, 100));
+ assertEquals(new Rect(10, 0, 100, 100), mSnapshotSurface.calculateSnapshotCrop());
}
@Test
public void testCalculateSnapshotCrop_navBarRight() {
- setupSurface(100, 100, new Rect(0, 10, 10, 0), 0, 0, new Rect(0, 0, 100, 100));
- assertEquals(new Rect(0, 0, 90, 100), mWindow.calculateSnapshotCrop());
+ setupSurface(100, 100, new Rect(0, 10, 10, 0), 0, new Rect(0, 0, 100, 100));
+ assertEquals(new Rect(0, 0, 90, 100), mSnapshotSurface.calculateSnapshotCrop());
}
@Test
public void testCalculateSnapshotCrop_waterfall() {
- setupSurface(100, 100, new Rect(5, 10, 5, 10), 0, 0, new Rect(0, 0, 100, 100));
- assertEquals(new Rect(5, 0, 95, 90), mWindow.calculateSnapshotCrop());
+ setupSurface(100, 100, new Rect(5, 10, 5, 10), 0, new Rect(0, 0, 100, 100));
+ assertEquals(new Rect(5, 0, 95, 90), mSnapshotSurface.calculateSnapshotCrop());
}
@Test
public void testCalculateSnapshotFrame() {
setupSurface(100, 100);
final Rect insets = new Rect(0, 10, 0, 10);
- mWindow.setFrames(new Rect(0, 0, 100, 100), insets);
+ mSnapshotSurface.setFrames(new Rect(0, 0, 100, 100), insets);
assertEquals(new Rect(0, 0, 100, 80),
- mWindow.calculateSnapshotFrame(new Rect(0, 10, 100, 90)));
+ mSnapshotSurface.calculateSnapshotFrame(new Rect(0, 10, 100, 90)));
}
@Test
public void testCalculateSnapshotFrame_navBarLeft() {
setupSurface(100, 100);
final Rect insets = new Rect(10, 10, 0, 0);
- mWindow.setFrames(new Rect(0, 0, 100, 100), insets);
+ mSnapshotSurface.setFrames(new Rect(0, 0, 100, 100), insets);
assertEquals(new Rect(10, 0, 100, 90),
- mWindow.calculateSnapshotFrame(new Rect(10, 10, 100, 100)));
+ mSnapshotSurface.calculateSnapshotFrame(new Rect(10, 10, 100, 100)));
}
@Test
public void testCalculateSnapshotFrame_waterfall() {
- setupSurface(100, 100, new Rect(5, 10, 5, 10), 0, 0, new Rect(0, 0, 100, 100));
+ setupSurface(100, 100, new Rect(5, 10, 5, 10), 0, new Rect(0, 0, 100, 100));
final Rect insets = new Rect(0, 10, 0, 10);
- mWindow.setFrames(new Rect(5, 0, 95, 100), insets);
+ mSnapshotSurface.setFrames(new Rect(5, 0, 95, 100), insets);
assertEquals(new Rect(0, 0, 90, 90),
- mWindow.calculateSnapshotFrame(new Rect(5, 0, 95, 90)));
+ mSnapshotSurface.calculateSnapshotFrame(new Rect(5, 0, 95, 90)));
}
@Test
public void testDrawStatusBarBackground() {
setupSurface(100, 100);
final Rect insets = new Rect(0, 10, 10, 0);
- mWindow.setFrames(new Rect(0, 0, 100, 100), insets);
+ mSnapshotSurface.setFrames(new Rect(0, 0, 100, 100), insets);
final Canvas mockCanvas = mock(Canvas.class);
when(mockCanvas.getWidth()).thenReturn(100);
when(mockCanvas.getHeight()).thenReturn(100);
- mWindow.drawStatusBarBackground(mockCanvas, new Rect(0, 0, 50, 100));
+ mSnapshotSurface.drawStatusBarBackground(mockCanvas, new Rect(0, 0, 50, 100));
verify(mockCanvas).drawRect(eq(50.0f), eq(0.0f), eq(90.0f), eq(10.0f), any());
}
@@ -234,11 +228,11 @@ public class TaskSnapshotWindowTest extends ShellTestCase {
public void testDrawStatusBarBackground_nullFrame() {
setupSurface(100, 100);
final Rect insets = new Rect(0, 10, 10, 0);
- mWindow.setFrames(new Rect(0, 0, 100, 100), insets);
+ mSnapshotSurface.setFrames(new Rect(0, 0, 100, 100), insets);
final Canvas mockCanvas = mock(Canvas.class);
when(mockCanvas.getWidth()).thenReturn(100);
when(mockCanvas.getHeight()).thenReturn(100);
- mWindow.drawStatusBarBackground(mockCanvas, null);
+ mSnapshotSurface.drawStatusBarBackground(mockCanvas, null);
verify(mockCanvas).drawRect(eq(0.0f), eq(0.0f), eq(90.0f), eq(10.0f), any());
}
@@ -246,50 +240,50 @@ public class TaskSnapshotWindowTest extends ShellTestCase {
public void testDrawStatusBarBackground_nope() {
setupSurface(100, 100);
final Rect insets = new Rect(0, 10, 10, 0);
- mWindow.setFrames(new Rect(0, 0, 100, 100), insets);
+ mSnapshotSurface.setFrames(new Rect(0, 0, 100, 100), insets);
final Canvas mockCanvas = mock(Canvas.class);
when(mockCanvas.getWidth()).thenReturn(100);
when(mockCanvas.getHeight()).thenReturn(100);
- mWindow.drawStatusBarBackground(mockCanvas, new Rect(0, 0, 100, 100));
+ mSnapshotSurface.drawStatusBarBackground(mockCanvas, new Rect(0, 0, 100, 100));
verify(mockCanvas, never()).drawRect(anyInt(), anyInt(), anyInt(), anyInt(), any());
}
@Test
public void testDrawNavigationBarBackground() {
final Rect insets = new Rect(0, 10, 0, 10);
- setupSurface(100, 100, insets, 0, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
+ setupSurface(100, 100, insets, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
new Rect(0, 0, 100, 100));
- mWindow.setFrames(new Rect(0, 0, 100, 100), insets);
+ mSnapshotSurface.setFrames(new Rect(0, 0, 100, 100), insets);
final Canvas mockCanvas = mock(Canvas.class);
when(mockCanvas.getWidth()).thenReturn(100);
when(mockCanvas.getHeight()).thenReturn(100);
- mWindow.drawNavigationBarBackground(mockCanvas);
+ mSnapshotSurface.drawNavigationBarBackground(mockCanvas);
verify(mockCanvas).drawRect(eq(new Rect(0, 90, 100, 100)), any());
}
@Test
public void testDrawNavigationBarBackground_left() {
final Rect insets = new Rect(10, 10, 0, 0);
- setupSurface(100, 100, insets, 0, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
+ setupSurface(100, 100, insets, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
new Rect(0, 0, 100, 100));
- mWindow.setFrames(new Rect(0, 0, 100, 100), insets);
+ mSnapshotSurface.setFrames(new Rect(0, 0, 100, 100), insets);
final Canvas mockCanvas = mock(Canvas.class);
when(mockCanvas.getWidth()).thenReturn(100);
when(mockCanvas.getHeight()).thenReturn(100);
- mWindow.drawNavigationBarBackground(mockCanvas);
+ mSnapshotSurface.drawNavigationBarBackground(mockCanvas);
verify(mockCanvas).drawRect(eq(new Rect(0, 0, 10, 100)), any());
}
@Test
public void testDrawNavigationBarBackground_right() {
final Rect insets = new Rect(0, 10, 10, 0);
- setupSurface(100, 100, insets, 0, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
+ setupSurface(100, 100, insets, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
new Rect(0, 0, 100, 100));
- mWindow.setFrames(new Rect(0, 0, 100, 100), insets);
+ mSnapshotSurface.setFrames(new Rect(0, 0, 100, 100), insets);
final Canvas mockCanvas = mock(Canvas.class);
when(mockCanvas.getWidth()).thenReturn(100);
when(mockCanvas.getHeight()).thenReturn(100);
- mWindow.drawNavigationBarBackground(mockCanvas);
+ mSnapshotSurface.drawNavigationBarBackground(mockCanvas);
verify(mockCanvas).drawRect(eq(new Rect(90, 0, 100, 100)), any());
}
}
diff --git a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
index f370ebd94545..9d6b29e5c072 100644
--- a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
+++ b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
@@ -17,6 +17,7 @@
package android.window;
import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
@@ -60,8 +61,8 @@ public class WindowOnBackInvokedDispatcherTest {
private OnBackAnimationCallback mCallback1;
@Mock
private OnBackAnimationCallback mCallback2;
- @Mock
- private BackEvent mBackEvent;
+ private final BackMotionEvent mBackEvent = new BackMotionEvent(
+ 0, 0, 0, BackEvent.EDGE_LEFT, null);
@Before
public void setUp() throws Exception {
@@ -89,12 +90,12 @@ public class WindowOnBackInvokedDispatcherTest {
captor.capture());
captor.getAllValues().get(0).getCallback().onBackStarted(mBackEvent);
waitForIdle();
- verify(mCallback1).onBackStarted(mBackEvent);
+ verify(mCallback1).onBackStarted(any(BackEvent.class));
verifyZeroInteractions(mCallback2);
captor.getAllValues().get(1).getCallback().onBackStarted(mBackEvent);
waitForIdle();
- verify(mCallback2).onBackStarted(mBackEvent);
+ verify(mCallback2).onBackStarted(any(BackEvent.class));
verifyNoMoreInteractions(mCallback1);
}
@@ -114,7 +115,7 @@ public class WindowOnBackInvokedDispatcherTest {
assertEquals(captor.getValue().getPriority(), OnBackInvokedDispatcher.PRIORITY_OVERLAY);
captor.getValue().getCallback().onBackStarted(mBackEvent);
waitForIdle();
- verify(mCallback1).onBackStarted(mBackEvent);
+ verify(mCallback1).onBackStarted(any(BackEvent.class));
}
@Test
@@ -152,6 +153,6 @@ public class WindowOnBackInvokedDispatcherTest {
verify(mWindowSession).setOnBackInvokedCallbackInfo(Mockito.eq(mWindow), captor.capture());
captor.getValue().getCallback().onBackStarted(mBackEvent);
waitForIdle();
- verify(mCallback2).onBackStarted(mBackEvent);
+ verify(mCallback2).onBackStarted(any(BackEvent.class));
}
}
diff --git a/core/tests/fuzzers/FuzzService/FuzzBinder.java b/core/tests/fuzzers/FuzzService/FuzzBinder.java
index 7096f52ab392..7fd199afaf85 100644
--- a/core/tests/fuzzers/FuzzService/FuzzBinder.java
+++ b/core/tests/fuzzers/FuzzService/FuzzBinder.java
@@ -15,6 +15,7 @@
*/
package randomparcel;
import android.os.IBinder;
+import android.os.Parcel;
public class FuzzBinder {
static {
@@ -33,6 +34,12 @@ public class FuzzBinder {
fuzzServiceInternal(binder, data);
}
+ // This API fills parcel object
+ public static void fillRandomParcel(Parcel parcel, byte[] data) {
+ fillParcelInternal(parcel, data);
+ }
+
private static native void fuzzServiceInternal(IBinder binder, byte[] data);
+ private static native void fillParcelInternal(Parcel parcel, byte[] data);
private static native int registerNatives();
}
diff --git a/core/tests/fuzzers/FuzzService/random_parcel_jni.cpp b/core/tests/fuzzers/FuzzService/random_parcel_jni.cpp
index c0528d5c7b9a..264aa5f26bc1 100644
--- a/core/tests/fuzzers/FuzzService/random_parcel_jni.cpp
+++ b/core/tests/fuzzers/FuzzService/random_parcel_jni.cpp
@@ -16,7 +16,9 @@
#include "random_parcel_jni.h"
#include <android_util_Binder.h>
+#include <android_os_Parcel.h>
#include <fuzzbinder/libbinder_driver.h>
+#include <fuzzbinder/random_parcel.h>
#include <fuzzer/FuzzedDataProvider.h>
using namespace android;
@@ -35,3 +37,15 @@ JNIEXPORT void JNICALL Java_randomparcel_FuzzBinder_fuzzServiceInternal(JNIEnv *
JNIEXPORT jint JNICALL Java_randomparcel_FuzzBinder_registerNatives(JNIEnv* env) {
return registerFrameworkNatives(env);
}
+
+JNIEXPORT void JNICALL Java_randomparcel_FuzzBinder_fillParcelInternal(JNIEnv *env, jobject thiz, jobject jparcel, jbyteArray fuzzData) {
+ size_t len = static_cast<size_t>(env->GetArrayLength(fuzzData));
+ uint8_t data[len];
+ env->GetByteArrayRegion(fuzzData, 0, len, reinterpret_cast<jbyte*>(data));
+
+ FuzzedDataProvider provider(data, len);
+ RandomParcelOptions options;
+
+ Parcel* parcel = parcelForJavaObject(env, jparcel);
+ fillRandomParcel(parcel, std::move(provider), &options);
+}
diff --git a/core/tests/fuzzers/FuzzService/random_parcel_jni.h b/core/tests/fuzzers/FuzzService/random_parcel_jni.h
index 20a4c9d46aa6..c96354a2b35b 100644
--- a/core/tests/fuzzers/FuzzService/random_parcel_jni.h
+++ b/core/tests/fuzzers/FuzzService/random_parcel_jni.h
@@ -23,4 +23,6 @@ extern "C" {
// Function from AndroidRuntime
jint registerFrameworkNatives(JNIEnv* env);
+
+ JNIEXPORT void JNICALL Java_randomparcel_FuzzBinder_fillParcelInternal(JNIEnv *env, jobject thiz, jobject parcel, jbyteArray fuzzData);
}
diff --git a/core/tests/fuzzers/ParcelFuzzer/Android.bp b/core/tests/fuzzers/ParcelFuzzer/Android.bp
new file mode 100644
index 000000000000..b71a06e3572e
--- /dev/null
+++ b/core/tests/fuzzers/ParcelFuzzer/Android.bp
@@ -0,0 +1,40 @@
+package {
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+java_fuzz {
+ name: "java_binder_parcel_fuzzer",
+ srcs: [
+ "ParcelFuzzer.java",
+ "ReadUtils.java",
+ "FuzzUtils.java",
+ "FuzzOperation.java",
+ "ReadOperation.java",
+ ":framework-core-sources-for-fuzzers",
+ ],
+ static_libs: [
+ "jazzer",
+ "random_parcel_lib",
+ "binderReadParcelIface-java",
+ ],
+ jni_libs: [
+ "librandom_parcel_jni",
+ "libc++",
+ "libandroid_runtime",
+ ],
+ libs: [
+ "framework",
+ "unsupportedappusage",
+ "ext",
+ "framework-res",
+ ],
+ native_bridge_supported: true,
+ fuzz_config: {
+ cc: [
+ "smoreland@google.com",
+ "waghpawan@google.com",
+ ],
+ // Adds bugs to hotlist "AIDL fuzzers bugs" on buganizer
+ hotlists: ["4637097"],
+ },
+}
diff --git a/core/tests/fuzzers/ParcelFuzzer/FuzzOperation.java b/core/tests/fuzzers/ParcelFuzzer/FuzzOperation.java
new file mode 100644
index 000000000000..033231df28be
--- /dev/null
+++ b/core/tests/fuzzers/ParcelFuzzer/FuzzOperation.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package parcelfuzzer;
+
+import com.code_intelligence.jazzer.api.FuzzedDataProvider;
+
+public interface FuzzOperation {
+ void doFuzz(FuzzedDataProvider data);
+}
diff --git a/core/tests/fuzzers/ParcelFuzzer/FuzzUtils.java b/core/tests/fuzzers/ParcelFuzzer/FuzzUtils.java
new file mode 100644
index 000000000000..5e9e5ecac4dc
--- /dev/null
+++ b/core/tests/fuzzers/ParcelFuzzer/FuzzUtils.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package parcelfuzzer;
+
+import android.os.Parcel;
+
+import com.code_intelligence.jazzer.api.FuzzedDataProvider;
+
+import randomparcel.FuzzBinder;
+
+public class FuzzUtils {
+ public static FuzzOperation[] FUZZ_OPERATIONS =
+ new FuzzOperation[] {
+ new FuzzOperation() {
+ @java.lang.Override
+ public void doFuzz(FuzzedDataProvider provider) {
+ // Fuzz Append
+ int start = provider.consumeInt();
+ int len = provider.consumeInt();
+ Parcel p1 = null;
+ Parcel p2 = null;
+
+ try {
+ p1 = Parcel.obtain();
+ p2 = Parcel.obtain();
+
+ byte[] data =
+ provider.consumeBytes(
+ provider.consumeInt(0, provider.remainingBytes()));
+ FuzzBinder.fillRandomParcel(p1, data);
+ FuzzBinder.fillRandomParcel(p2, provider.consumeRemainingAsBytes());
+
+ p1.appendFrom(p2, start, len);
+
+ } catch (Exception e) {
+ // Rethrow exception as runtime exceptions are catched
+ // at highest level.
+ throw e;
+ } finally {
+ p1.recycle();
+ p2.recycle();
+ }
+ }
+ },
+ new FuzzOperation() {
+ @java.lang.Override
+ public void doFuzz(FuzzedDataProvider provider) {
+ // Fuzz Read
+ // Use maximum bytes to generate read instructions and remaining for parcel
+ // creation
+ int maxParcelBytes = provider.remainingBytes() / 3;
+ byte[] data = provider.consumeBytes(maxParcelBytes);
+ Parcel randomParcel = null;
+
+ try {
+ randomParcel = Parcel.obtain();
+ FuzzBinder.fillRandomParcel(randomParcel, data);
+
+ while (provider.remainingBytes() > 0) {
+ provider.pickValue(ReadUtils.READ_OPERATIONS)
+ .readParcel(randomParcel, provider);
+ }
+
+ } catch (Exception e) {
+ // Rethrow exception as runtime exceptions are catched
+ // at highest level.
+ throw e;
+ } finally {
+ randomParcel.recycle();
+ }
+ }
+ },
+ };
+}
diff --git a/core/tests/fuzzers/ParcelFuzzer/ParcelFuzzer.java b/core/tests/fuzzers/ParcelFuzzer/ParcelFuzzer.java
new file mode 100644
index 000000000000..688c8126c99e
--- /dev/null
+++ b/core/tests/fuzzers/ParcelFuzzer/ParcelFuzzer.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package parcelfuzzer;
+
+import android.util.Log;
+
+import com.code_intelligence.jazzer.api.FuzzedDataProvider;
+
+import randomparcel.FuzzBinder;
+
+public class ParcelFuzzer {
+
+ static {
+ // Initialize JNI dependencies
+ FuzzBinder.init();
+ }
+
+ public static void fuzzerTestOneInput(FuzzedDataProvider provider) {
+ // Default behavior for Java APIs is to throw RuntimeException.
+ // We need to fuzz to detect other problems which are not handled explicitly.
+ // TODO(b/150808347): Change known API exceptions to subclass of
+ // RuntimeExceptions and catch those only.
+ try {
+ provider.pickValue(FuzzUtils.FUZZ_OPERATIONS).doFuzz(provider);
+ } catch (RuntimeException e) {
+ Log.e("ParcelFuzzer", "Exception occurred while fuzzing ", e);
+ }
+ }
+}
diff --git a/core/tests/fuzzers/ParcelFuzzer/ReadOperation.java b/core/tests/fuzzers/ParcelFuzzer/ReadOperation.java
new file mode 100644
index 000000000000..5c227e3525fd
--- /dev/null
+++ b/core/tests/fuzzers/ParcelFuzzer/ReadOperation.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package parcelfuzzer;
+
+import android.os.Parcel;
+
+import com.code_intelligence.jazzer.api.FuzzedDataProvider;
+
+public interface ReadOperation {
+ void readParcel(Parcel parcel, FuzzedDataProvider provider);
+}
diff --git a/core/tests/fuzzers/ParcelFuzzer/ReadUtils.java b/core/tests/fuzzers/ParcelFuzzer/ReadUtils.java
new file mode 100644
index 000000000000..0eff5f24f7e0
--- /dev/null
+++ b/core/tests/fuzzers/ParcelFuzzer/ReadUtils.java
@@ -0,0 +1,435 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package parcelfuzzer;
+
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.IInterface;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import parcelables.EmptyParcelable;
+import parcelables.GenericDataParcelable;
+import parcelables.SingleDataParcelable;
+
+public class ReadUtils {
+ public static int MAX_LEN = 1000000;
+ public static int MIN_LEN = 0;
+
+ private static class SomeParcelable implements Parcelable {
+ private final int mValue;
+
+ private SomeParcelable(Parcel in) {
+ this.mValue = in.readInt();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(mValue);
+ }
+
+ public static Parcelable.Creator<SomeParcelable> CREATOR =
+ new Parcelable.Creator<SomeParcelable>() {
+
+ @Override
+ public SomeParcelable createFromParcel(Parcel source) {
+ return new SomeParcelable(source);
+ }
+
+ @Override
+ public SomeParcelable[] newArray(int size) {
+ return new SomeParcelable[size];
+ }
+ };
+ }
+
+ private static class TestClassLoader extends ClassLoader {
+ TestClassLoader() {
+ super();
+ }
+ }
+
+ private static class TestInterface implements IInterface {
+ public Binder binder;
+ private static final String DESCRIPTOR = "TestInterface";
+
+ TestInterface() {
+ binder = new Binder();
+ binder.attachInterface(this, DESCRIPTOR);
+ }
+
+ public IBinder asBinder() {
+ return binder;
+ }
+
+ public static TestInterface asInterface(IBinder binder) {
+ if (binder != null) {
+ IInterface iface = binder.queryLocalInterface(DESCRIPTOR);
+ if (iface != null && iface instanceof TestInterface) {
+ return (TestInterface) iface;
+ }
+ }
+ return null;
+ }
+ }
+
+ public static ReadOperation[] READ_OPERATIONS =
+ new ReadOperation[] {
+ (parcel, provider) -> {
+ parcel.setDataPosition(provider.consumeInt());
+ },
+ (parcel, provider) -> {
+ parcel.setDataCapacity(provider.consumeInt());
+ },
+ (parcel, provider) -> {
+ parcel.setDataSize(provider.consumeInt());
+ },
+ (parcel, provider) -> {
+ parcel.dataSize();
+ },
+ (parcel, provider) -> {
+ parcel.dataPosition();
+ },
+ (parcel, provider) -> {
+ parcel.dataCapacity();
+ },
+
+ // read basic types
+ (parcel, provider) -> {
+ parcel.readByte();
+ },
+ (parcel, provider) -> {
+ parcel.readBoolean();
+ },
+ (parcel, provider) -> {
+ parcel.readInt();
+ },
+ (parcel, provider) -> {
+ parcel.readLong();
+ },
+ (parcel, provider) -> {
+ parcel.readFloat();
+ },
+ (parcel, provider) -> {
+ parcel.readDouble();
+ },
+ (parcel, provider) -> {
+ parcel.readString();
+ },
+ (parcel, provider) -> {
+ parcel.readString8();
+ },
+ (parcel, provider) -> {
+ parcel.readString16();
+ },
+ (parcel, provider) -> {
+ parcel.readBlob();
+ },
+ (parcel, provider) -> {
+ parcel.readStrongBinder();
+ },
+
+ // read arrays of random length
+ (parcel, provider) -> {
+ byte[] array;
+ if (provider.consumeBoolean()) {
+ int pos = parcel.dataPosition();
+ array = new byte[Math.min(MAX_LEN, parcel.readInt())];
+ parcel.setDataPosition(pos);
+ } else {
+ array = new byte[provider.consumeInt(MIN_LEN, MAX_LEN)];
+ }
+ parcel.readByteArray(array);
+ },
+ (parcel, provider) -> {
+ char[] array;
+ if (provider.consumeBoolean()) {
+ int pos = parcel.dataPosition();
+ array = new char[Math.min(MAX_LEN, parcel.readInt())];
+ parcel.setDataPosition(pos);
+ } else {
+ array = new char[provider.consumeInt(MIN_LEN, MAX_LEN)];
+ }
+ parcel.readCharArray(array);
+ },
+ (parcel, provider) -> {
+ int[] array;
+ if (provider.consumeBoolean()) {
+ int pos = parcel.dataPosition();
+ array = new int[Math.min(MAX_LEN, parcel.readInt())];
+ parcel.setDataPosition(pos);
+ } else {
+ array = new int[provider.consumeInt(MIN_LEN, MAX_LEN)];
+ }
+ parcel.readIntArray(array);
+ },
+ (parcel, provider) -> {
+ double[] array;
+ if (provider.consumeBoolean()) {
+ int pos = parcel.dataPosition();
+ array = new double[Math.min(MAX_LEN, parcel.readInt())];
+ parcel.setDataPosition(pos);
+ } else {
+ array = new double[provider.consumeInt(MIN_LEN, MAX_LEN)];
+ }
+ parcel.readDoubleArray(array);
+ },
+ (parcel, provider) -> {
+ float[] array;
+ if (provider.consumeBoolean()) {
+ int pos = parcel.dataPosition();
+ array = new float[Math.min(MAX_LEN, parcel.readInt())];
+ parcel.setDataPosition(pos);
+ } else {
+ array = new float[provider.consumeInt(MIN_LEN, MAX_LEN)];
+ }
+ parcel.readFloatArray(array);
+ },
+ (parcel, provider) -> {
+ boolean[] array;
+ if (provider.consumeBoolean()) {
+ int pos = parcel.dataPosition();
+ array = new boolean[Math.min(MAX_LEN, parcel.readInt())];
+ parcel.setDataPosition(pos);
+ } else {
+ array = new boolean[provider.consumeInt(MIN_LEN, MAX_LEN)];
+ }
+ parcel.readBooleanArray(array);
+ },
+ (parcel, provider) -> {
+ long[] array;
+ if (provider.consumeBoolean()) {
+ int pos = parcel.dataPosition();
+ array = new long[Math.min(MAX_LEN, parcel.readInt())];
+ parcel.setDataPosition(pos);
+ } else {
+ array = new long[provider.consumeInt(MIN_LEN, MAX_LEN)];
+ }
+ parcel.readLongArray(array);
+ },
+ (parcel, provider) -> {
+ IBinder[] array;
+ if (provider.consumeBoolean()) {
+ int pos = parcel.dataPosition();
+ array = new IBinder[Math.min(MAX_LEN, parcel.readInt())];
+ parcel.setDataPosition(pos);
+ } else {
+ array = new IBinder[provider.consumeInt(MIN_LEN, MAX_LEN)];
+ }
+ parcel.readBinderArray(array);
+ },
+ (parcel, provider) -> {
+ ArrayList<IBinder> arrayList = new ArrayList<IBinder>();
+ parcel.readBinderList(arrayList);
+ },
+
+ // unmarshall from random parcel data and random bytes
+ (parcel, provider) -> {
+ byte[] data = parcel.marshall();
+ Parcel p = Parcel.obtain();
+ p.unmarshall(data, provider.consumeInt(), provider.consumeInt());
+ p.recycle();
+ },
+ (parcel, provider) -> {
+ byte[] data = provider.consumeRemainingAsBytes();
+ Parcel p = Parcel.obtain();
+ p.unmarshall(data, provider.consumeInt(), provider.consumeInt());
+ p.recycle();
+ },
+ (parcel, provider) -> {
+ parcel.hasFileDescriptors(provider.consumeInt(), provider.consumeInt());
+ },
+
+ // read AIDL generated parcelables
+ (parcel, provider) -> {
+ TestClassLoader loader = new TestClassLoader();
+ parcel.readParcelable(loader, SingleDataParcelable.class);
+ },
+ (parcel, provider) -> {
+ TestClassLoader loader = new TestClassLoader();
+ parcel.readParcelableArray(loader, SingleDataParcelable.class);
+ },
+ (parcel, provider) -> {
+ SingleDataParcelable[] array;
+ if (provider.consumeBoolean()) {
+ int pos = parcel.dataPosition();
+ array = new SingleDataParcelable[Math.min(MAX_LEN, parcel.readInt())];
+ parcel.setDataPosition(pos);
+ } else {
+ array = new SingleDataParcelable[provider.consumeInt(MIN_LEN, MAX_LEN)];
+ }
+ parcel.readTypedArray(array, SingleDataParcelable.CREATOR);
+ },
+ (parcel, provider) -> {
+ TestClassLoader loader = new TestClassLoader();
+ parcel.readParcelable(loader, EmptyParcelable.class);
+ },
+ (parcel, provider) -> {
+ TestClassLoader loader = new TestClassLoader();
+ parcel.readParcelableArray(loader, EmptyParcelable.class);
+ },
+ (parcel, provider) -> {
+ EmptyParcelable[] array;
+ if (provider.consumeBoolean()) {
+ int pos = parcel.dataPosition();
+ array = new EmptyParcelable[Math.min(MAX_LEN, parcel.readInt())];
+ parcel.setDataPosition(pos);
+ } else {
+ array = new EmptyParcelable[provider.consumeInt(MIN_LEN, MAX_LEN)];
+ }
+ parcel.readTypedArray(array, EmptyParcelable.CREATOR);
+ },
+ (parcel, provider) -> {
+ TestClassLoader loader = new TestClassLoader();
+ parcel.readParcelable(loader, GenericDataParcelable.class);
+ },
+ (parcel, provider) -> {
+ TestClassLoader loader = new TestClassLoader();
+ parcel.readParcelableArray(loader, GenericDataParcelable.class);
+ },
+ (parcel, provider) -> {
+ GenericDataParcelable[] array;
+ if (provider.consumeBoolean()) {
+ int pos = parcel.dataPosition();
+ array = new GenericDataParcelable[Math.min(MAX_LEN, parcel.readInt())];
+ parcel.setDataPosition(pos);
+ } else {
+ int len = provider.consumeInt(MIN_LEN, MAX_LEN);
+ array = new GenericDataParcelable[len];
+ }
+ parcel.readTypedArray(array, GenericDataParcelable.CREATOR);
+ },
+
+ // read parcelables
+ (parcel, provider) -> {
+ TestClassLoader loader = new TestClassLoader();
+ parcel.readParcelable(loader, SomeParcelable.class);
+ },
+ (parcel, provider) -> {
+ TestClassLoader loader = new TestClassLoader();
+ parcel.readParcelableArray(loader, SomeParcelable.class);
+ },
+ (parcel, provider) -> {
+ SomeParcelable[] array;
+ if (provider.consumeBoolean()) {
+ int pos = parcel.dataPosition();
+ array = new SomeParcelable[Math.min(MAX_LEN, parcel.readInt())];
+ parcel.setDataPosition(pos);
+ } else {
+ array = new SomeParcelable[provider.consumeInt(MIN_LEN, MAX_LEN)];
+ }
+ parcel.readTypedArray(array, SomeParcelable.CREATOR);
+ },
+ (parcel, provider) -> {
+ TestClassLoader loader = new TestClassLoader();
+ parcel.readParcelableArray(loader);
+ },
+ (parcel, provider) -> {
+ parcel.hasFileDescriptors(provider.consumeInt(), provider.consumeInt());
+ },
+ (parcel, provider) -> {
+ TestClassLoader loader = new TestClassLoader();
+ parcel.readParcelableArray(loader);
+ },
+
+ // read lists
+ (parcel, provider) -> {
+ TestClassLoader loader = new TestClassLoader();
+ parcel.readArrayList(loader);
+ },
+ (parcel, provider) -> {
+ TestClassLoader loader = new TestClassLoader();
+ parcel.readArrayList(loader, Object.class);
+ },
+ (parcel, provider) -> {
+ TestClassLoader loader = new TestClassLoader();
+ parcel.readArrayList(loader, SomeParcelable.class);
+ },
+
+ // read sparse arrays
+ (parcel, provider) -> {
+ TestClassLoader loader = new TestClassLoader();
+ parcel.readSparseArray(loader);
+ },
+ (parcel, provider) -> {
+ TestClassLoader loader = new TestClassLoader();
+ parcel.readSparseArray(loader, Object.class);
+ },
+ (parcel, provider) -> {
+ TestClassLoader loader = new TestClassLoader();
+ parcel.readSparseArray(loader, SomeParcelable.class);
+ },
+ (parcel, provider) -> {
+ TestClassLoader loader = new TestClassLoader();
+ parcel.readSerializable(loader, Object.class);
+ },
+
+ // read interface
+ (parcel, provider) -> {
+ TestInterface[] array;
+ if (provider.consumeBoolean()) {
+ int pos = parcel.dataPosition();
+ array = new TestInterface[Math.min(MAX_LEN, parcel.readInt())];
+ parcel.setDataPosition(pos);
+ } else {
+ array = new TestInterface[provider.consumeInt(MIN_LEN, MAX_LEN)];
+ }
+ parcel.readInterfaceArray(array, TestInterface::asInterface);
+ },
+ (parcel, provider) -> {
+ int w = provider.consumeInt(MIN_LEN, MAX_LEN);
+ int h = provider.consumeInt(MIN_LEN, MAX_LEN);
+ TestInterface[][] array = new TestInterface[w][h];
+ parcel.readFixedArray(array, TestInterface::asInterface);
+ },
+ (parcel, provider) -> {
+ ArrayList<TestInterface> array = new ArrayList<TestInterface>();
+ parcel.readInterfaceList(array, TestInterface::asInterface);
+ },
+
+ // read bundle
+ (parcel, provider) -> {
+ TestClassLoader loader = new TestClassLoader();
+ parcel.readBundle(loader);
+ },
+ (parcel, provider) -> {
+ parcel.readBundle();
+ },
+
+ // read HashMap
+ (parcel, provider) -> {
+ TestClassLoader loader = new TestClassLoader();
+ parcel.readHashMap(loader);
+ },
+ (parcel, provider) -> {
+ TestClassLoader loader = new TestClassLoader();
+ parcel.readHashMap(loader, String.class, String.class);
+ },
+ (parcel, provider) -> {
+ HashMap<String, String> hashMap = new HashMap<>();
+ TestClassLoader loader = new TestClassLoader();
+ parcel.readMap(hashMap, loader, String.class, String.class);
+ },
+ };
+}
diff --git a/core/tests/fuzzers/java_service_fuzzer/Android.bp b/core/tests/fuzzers/java_service_fuzzer/Android.bp
index 625de143a685..6acb19852210 100644
--- a/core/tests/fuzzers/java_service_fuzzer/Android.bp
+++ b/core/tests/fuzzers/java_service_fuzzer/Android.bp
@@ -37,4 +37,12 @@ java_fuzz {
"framework-res",
],
native_bridge_supported: true,
+ fuzz_config: {
+ cc: [
+ "smoreland@google.com",
+ "waghpawan@google.com",
+ ],
+ // Adds bugs to hotlist "AIDL fuzzers bugs" on buganizer
+ hotlists: ["4637097"],
+ },
}
diff --git a/core/tests/mockingcoretests/src/android/view/DisplayTest.java b/core/tests/mockingcoretests/src/android/view/DisplayTest.java
index 0c939ec24643..9ccf3b3c9d05 100644
--- a/core/tests/mockingcoretests/src/android/view/DisplayTest.java
+++ b/core/tests/mockingcoretests/src/android/view/DisplayTest.java
@@ -27,6 +27,8 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSess
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertArrayEquals;
+
import android.app.WindowConfiguration;
import android.content.Context;
import android.content.res.Resources;
@@ -399,6 +401,15 @@ public class DisplayTest {
verifyRealMetricsMatchesBounds(display, sDeviceBoundsLandscape);
}
+ @Test
+ public void testSupportedHdrTypesForDisplayModeAreSorted() {
+ int[] nonSortedHdrTypes = new int[]{3, 2, 1};
+ Display.Mode displayMode = new Display.Mode(0, 0, 0, 0, new float[0], nonSortedHdrTypes);
+
+ int[] sortedHdrTypes = new int[]{1, 2, 3};
+ assertArrayEquals(sortedHdrTypes, displayMode.getSupportedHdrTypes());
+ }
+
// Given rotated display dimensions, calculate the letterboxed app bounds.
private static Rect buildAppBounds(int displayWidth, int displayHeight) {
final int midWidth = displayWidth / 2;
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 4cc06e33ab62..33467404e38f 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -4171,6 +4171,12 @@
"group": "WM_DEBUG_REMOTE_ANIMATIONS",
"at": "com\/android\/server\/wm\/RemoteAnimationController.java"
},
+ "1945495497": {
+ "message": "Focused window didn't have a valid surface drawn.",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_BACK_PREVIEW",
+ "at": "com\/android\/server\/wm\/BackNavigationController.java"
+ },
"1947239194": {
"message": "Deferring rotation, still finishing previous rotation",
"level": "VERBOSE",
diff --git a/graphics/java/android/graphics/BaseCanvas.java b/graphics/java/android/graphics/BaseCanvas.java
index 54d64280c6f7..e62ac466ac43 100644
--- a/graphics/java/android/graphics/BaseCanvas.java
+++ b/graphics/java/android/graphics/BaseCanvas.java
@@ -96,7 +96,7 @@ public abstract class BaseCanvas {
// These are also implemented in RecordingCanvas so that we can
// selectively apply on them
// Everything below here is copy/pasted from Canvas.java
- // The JNI registration is handled by android_view_Canvas.cpp
+ // The JNI registration is handled by android_graphics_Canvas.cpp
// ---------------------------------------------------------------------------
public void drawArc(float left, float top, float right, float bottom, float startAngle,
@@ -670,6 +670,17 @@ public abstract class BaseCanvas {
/**
* @hide
*/
+ public void drawMesh(Mesh mesh, BlendMode blendMode, Paint paint) {
+ if (!isHardwareAccelerated() && onHwFeatureInSwMode()) {
+ throw new RuntimeException("software rendering doesn't support meshes");
+ }
+ nDrawMesh(this.mNativeCanvasWrapper, mesh.getNativeWrapperInstance(),
+ blendMode.getXfermode().porterDuffMode, paint.getNativeInstance());
+ }
+
+ /**
+ * @hide
+ */
public void punchHole(float left, float top, float right, float bottom, float rx, float ry,
float alpha) {
nPunchHole(mNativeCanvasWrapper, left, top, right, bottom, rx, ry, alpha);
@@ -801,6 +812,9 @@ public abstract class BaseCanvas {
int vertOffset, float[] texs, int texOffset, int[] colors, int colorOffset,
short[] indices, int indexOffset, int indexCount, long nativePaint);
+ private static native void nDrawMesh(
+ long nativeCanvas, long nativeMesh, int mode, long nativePaint);
+
private static native void nDrawGlyphs(long nativeCanvas, int[] glyphIds, float[] positions,
int glyphIdStart, int positionStart, int glyphCount, long nativeFont, long nativePaint);
diff --git a/graphics/java/android/graphics/BaseRecordingCanvas.java b/graphics/java/android/graphics/BaseRecordingCanvas.java
index 1ba79b87e87c..eeff694eb3ac 100644
--- a/graphics/java/android/graphics/BaseRecordingCanvas.java
+++ b/graphics/java/android/graphics/BaseRecordingCanvas.java
@@ -606,6 +606,12 @@ public class BaseRecordingCanvas extends Canvas {
indices, indexOffset, indexCount, paint.getNativeInstance());
}
+ @Override
+ public final void drawMesh(Mesh mesh, BlendMode blendMode, Paint paint) {
+ nDrawMesh(mNativeCanvasWrapper, mesh.getNativeWrapperInstance(),
+ blendMode.getXfermode().porterDuffMode, paint.getNativeInstance());
+ }
+
/**
* @hide
*/
@@ -708,6 +714,10 @@ public class BaseRecordingCanvas extends Canvas {
long nativePaint);
@FastNative
+ private static native void nDrawMesh(
+ long canvasHandle, long nativeMesh, int mode, long nativePaint);
+
+ @FastNative
private static native void nDrawVertices(long nativeCanvas, int mode, int n, float[] verts,
int vertOffset, float[] texs, int texOffset, int[] colors, int colorOffset,
short[] indices, int indexOffset, int indexCount, long nativePaint);
diff --git a/graphics/java/android/graphics/ImageFormat.java b/graphics/java/android/graphics/ImageFormat.java
index c93b733a3f63..68f29278f282 100644
--- a/graphics/java/android/graphics/ImageFormat.java
+++ b/graphics/java/android/graphics/ImageFormat.java
@@ -26,7 +26,7 @@ public class ImageFormat {
@Retention(RetentionPolicy.SOURCE)
@IntDef(value = {
UNKNOWN,
- /**
+ /*
* Since some APIs accept either ImageFormat or PixelFormat (and the two
* enums do not overlap since they're both partial versions of the
* internal format enum), add PixelFormat values here so linting
diff --git a/graphics/java/android/graphics/Mesh.java b/graphics/java/android/graphics/Mesh.java
index f0a0cf40ffa9..f32e0ee0cc79 100644
--- a/graphics/java/android/graphics/Mesh.java
+++ b/graphics/java/android/graphics/Mesh.java
@@ -196,7 +196,6 @@ public class Mesh {
}
nativeUpdateUniforms(
mNativeMeshWrapper, uniformName, value1, value2, value3, value4, count);
- nativeUpdateMesh(mNativeMeshWrapper);
}
private void setUniform(String uniformName, float[] values, boolean isColor) {
@@ -208,7 +207,6 @@ public class Mesh {
}
nativeUpdateUniforms(mNativeMeshWrapper, uniformName, values, isColor);
- nativeUpdateMesh(mNativeMeshWrapper);
}
/**
@@ -271,7 +269,14 @@ public class Mesh {
throw new NullPointerException("The uniform values parameter must not be null");
}
nativeUpdateUniforms(mNativeMeshWrapper, uniformName, values);
- nativeUpdateMesh(mNativeMeshWrapper);
+ }
+
+ /**
+ * @hide so only calls from module can utilize it
+ */
+ long getNativeWrapperInstance() {
+ nativeUpdateMesh(mNativeMeshWrapper, mIsIndexed);
+ return mNativeMeshWrapper;
}
private void setIntUniform(
@@ -282,7 +287,6 @@ public class Mesh {
nativeUpdateUniforms(
mNativeMeshWrapper, uniformName, value1, value2, value3, value4, count);
- nativeUpdateMesh(mNativeMeshWrapper);
}
private Mesh(long nativeMeshWrapper, boolean isIndexed) {
@@ -313,5 +317,5 @@ public class Mesh {
private static native void nativeUpdateUniforms(long builder, String uniformName, int[] values);
- private static native void nativeUpdateMesh(long nativeMeshWrapper);
+ private static native void nativeUpdateMesh(long nativeMeshWrapper, boolean mIsIndexed);
}
diff --git a/keystore/java/android/security/KeyStoreException.java b/keystore/java/android/security/KeyStoreException.java
index 1a81dda8d56c..6536e43f67fd 100644
--- a/keystore/java/android/security/KeyStoreException.java
+++ b/keystore/java/android/security/KeyStoreException.java
@@ -138,6 +138,16 @@ public class KeyStoreException extends Exception {
* provisioning server refuses key issuance, this is a permanent error.</p>
*/
public static final int ERROR_ATTESTATION_KEYS_UNAVAILABLE = 16;
+ /**
+ * This device requires a software upgrade to use the key provisioning server. The software
+ * is outdated and this error is returned only on devices that rely solely on
+ * remotely-provisioned keys (see <a href=
+ * "https://android-developers.googleblog.com/2022/03/upgrading-android-attestation-remote.html"
+ * >Remote Key Provisioning</a>).
+ *
+ * @hide
+ */
+ public static final int ERROR_DEVICE_REQUIRES_UPGRADE_FOR_ATTESTATION = 17;
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@@ -157,7 +167,8 @@ public class KeyStoreException extends Exception {
ERROR_INCORRECT_USAGE,
ERROR_KEY_NOT_TEMPORALLY_VALID,
ERROR_KEY_OPERATION_EXPIRED,
- ERROR_ATTESTATION_KEYS_UNAVAILABLE
+ ERROR_ATTESTATION_KEYS_UNAVAILABLE,
+ ERROR_DEVICE_REQUIRES_UPGRADE_FOR_ATTESTATION,
})
public @interface PublicErrorCode {
}
@@ -184,6 +195,16 @@ public class KeyStoreException extends Exception {
* This value is returned when {@link #isTransientFailure()} is {@code true}.
*/
public static final int RETRY_WHEN_CONNECTIVITY_AVAILABLE = 3;
+ /**
+ * Re-try the operation that led to this error when the device has a software update
+ * downloaded and on the next reboot. The Remote provisioning server recognizes
+ * the device, but refuses issuance of attestation keys because it contains a software
+ * version that could potentially be vulnerable and needs an update. Re-trying after the
+ * device has upgraded and rebooted may alleviate the problem.
+ *
+ * <p>This value is returned when {@link #isTransientFailure()} is {@code true}.
+ */
+ public static final int RETRY_AFTER_NEXT_REBOOT = 4;
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@@ -191,6 +212,7 @@ public class KeyStoreException extends Exception {
RETRY_NEVER,
RETRY_WITH_EXPONENTIAL_BACKOFF,
RETRY_WHEN_CONNECTIVITY_AVAILABLE,
+ RETRY_AFTER_NEXT_REBOOT,
})
public @interface RetryPolicy {
}
@@ -217,6 +239,13 @@ public class KeyStoreException extends Exception {
* when the device has connectivity again.
* @hide */
public static final int RKP_FETCHING_PENDING_CONNECTIVITY = 3;
+ /**
+ * The RKP server recognizes the device, but the device may be running vulnerable software,
+ * and thus refusing issuance of RKP keys to it.
+ *
+ * @hide
+ */
+ public static final int RKP_FETCHING_PENDING_SOFTWARE_REBOOT = 4;
// Constants for encoding information about the error encountered:
// Whether the error relates to the system state/implementation as a whole, or a specific key.
@@ -236,7 +265,7 @@ public class KeyStoreException extends Exception {
private static int initializeRkpStatusForRegularErrors(int errorCode) {
// Check if the system code mistakenly called a constructor of KeyStoreException with
// the OUT_OF_KEYS error code but without RKP status.
- if (errorCode == ResponseCode.OUT_OF_KEYS) {
+ if (isRkpRelatedError(errorCode)) {
Log.e(TAG, "RKP error code without RKP status");
// Set RKP status to RKP_SERVER_REFUSED_ISSUANCE so that the caller never retries.
return RKP_SERVER_REFUSED_ISSUANCE;
@@ -272,7 +301,7 @@ public class KeyStoreException extends Exception {
super(message);
mErrorCode = errorCode;
mRkpStatus = rkpStatus;
- if (mErrorCode != ResponseCode.OUT_OF_KEYS) {
+ if (!isRkpRelatedError(mErrorCode)) {
Log.e(TAG, "Providing RKP status for error code " + errorCode + " has no effect.");
}
}
@@ -309,10 +338,11 @@ public class KeyStoreException extends Exception {
public boolean isTransientFailure() {
PublicErrorInformation failureInfo = getErrorInformation(mErrorCode);
// Special-case handling for RKP failures:
- if (mRkpStatus != RKP_SUCCESS && mErrorCode == ResponseCode.OUT_OF_KEYS) {
+ if (mRkpStatus != RKP_SUCCESS && isRkpRelatedError(mErrorCode)) {
switch (mRkpStatus) {
case RKP_TEMPORARILY_UNAVAILABLE:
case RKP_FETCHING_PENDING_CONNECTIVITY:
+ case RKP_FETCHING_PENDING_SOFTWARE_REBOOT:
return true;
case RKP_SERVER_REFUSED_ISSUANCE:
default:
@@ -346,6 +376,11 @@ public class KeyStoreException extends Exception {
return (failureInfo.indicators & IS_SYSTEM_ERROR) != 0;
}
+ private static boolean isRkpRelatedError(int errorCode) {
+ return errorCode == ResponseCode.OUT_OF_KEYS
+ || errorCode == ResponseCode.OUT_OF_KEYS_REQUIRES_UPGRADE;
+ }
+
/**
* Returns the re-try policy for transient failures. Valid only if
* {@link #isTransientFailure()} returns {@code True}.
@@ -362,6 +397,8 @@ public class KeyStoreException extends Exception {
return RETRY_WHEN_CONNECTIVITY_AVAILABLE;
case RKP_SERVER_REFUSED_ISSUANCE:
return RETRY_NEVER;
+ case RKP_FETCHING_PENDING_SOFTWARE_REBOOT:
+ return RETRY_AFTER_NEXT_REBOOT;
default:
return (failureInfo.indicators & IS_TRANSIENT_ERROR) != 0
? RETRY_WITH_EXPONENTIAL_BACKOFF : RETRY_NEVER;
@@ -620,5 +657,8 @@ public class KeyStoreException extends Exception {
new PublicErrorInformation(0, ERROR_KEY_DOES_NOT_EXIST));
sErrorCodeToFailureInfo.put(ResponseCode.OUT_OF_KEYS,
new PublicErrorInformation(IS_SYSTEM_ERROR, ERROR_ATTESTATION_KEYS_UNAVAILABLE));
+ sErrorCodeToFailureInfo.put(ResponseCode.OUT_OF_KEYS_REQUIRES_UPGRADE,
+ new PublicErrorInformation(IS_SYSTEM_ERROR | IS_TRANSIENT_ERROR,
+ ERROR_DEVICE_REQUIRES_UPGRADE_FOR_ATTESTATION));
}
}
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
index acc0005154b4..2830d7effa99 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
@@ -52,6 +52,7 @@ import android.system.keystore2.KeyEntryResponse;
import android.system.keystore2.KeyMetadata;
import android.system.keystore2.ResponseCode;
import android.telephony.TelephonyManager;
+import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Log;
@@ -647,6 +648,7 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato
// {@link android.security.KeyStoreException#RKP_TEMPORARILY_UNAVAILABLE},
// {@link android.security.KeyStoreException#RKP_SERVER_REFUSED_ISSUANCE},
// {@link android.security.KeyStoreException#RKP_FETCHING_PENDING_CONNECTIVITY}
+ // {@link android.security.KeyStoreException#RKP_FETCHING_PENDING_SOFTWARE_REBOOT}
public final int rkpStatus;
@Nullable
public final KeyPair keyPair;
@@ -856,6 +858,13 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato
KeymasterDefs.KM_TAG_ATTESTATION_ID_IMEI,
imei.getBytes(StandardCharsets.UTF_8)
));
+ final String secondImei = telephonyService.getImei(1);
+ if (!TextUtils.isEmpty(secondImei)) {
+ params.add(KeyStore2ParameterUtils.makeBytes(
+ KeymasterDefs.KM_TAG_ATTESTATION_ID_SECOND_IMEI,
+ secondImei.getBytes(StandardCharsets.UTF_8)
+ ));
+ }
break;
}
case AttestationUtils.ID_TYPE_MEID: {
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
index d7d43aa19757..b910287aa535 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
@@ -133,8 +133,18 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
}
// Create a TaskFragment for the secondary activity.
- createTaskFragmentAndStartActivity(wct, secondaryFragmentToken, ownerToken,
- secondaryFragmentBounds, windowingMode, activityIntent,
+ final TaskFragmentCreationParams fragmentOptions = new TaskFragmentCreationParams.Builder(
+ getOrganizerToken(), secondaryFragmentToken, ownerToken)
+ .setInitialBounds(secondaryFragmentBounds)
+ .setWindowingMode(windowingMode)
+ // Make sure to set the paired fragment token so that the new TaskFragment will be
+ // positioned right above the paired TaskFragment.
+ // This is needed in case we need to launch a placeholder Activity to split below a
+ // transparent always-expand Activity.
+ .setPairedPrimaryFragmentToken(launchingFragmentToken)
+ .build();
+ createTaskFragment(wct, fragmentOptions);
+ wct.startActivityInTaskFragment(secondaryFragmentToken, ownerToken, activityIntent,
activityOptions);
// Set adjacent to each other so that the containers below will be invisible.
@@ -173,8 +183,21 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
*/
void createTaskFragment(@NonNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken,
@NonNull IBinder ownerToken, @NonNull Rect bounds, @WindowingMode int windowingMode) {
- final TaskFragmentCreationParams fragmentOptions =
- createFragmentOptions(fragmentToken, ownerToken, bounds, windowingMode);
+ final TaskFragmentCreationParams fragmentOptions = new TaskFragmentCreationParams.Builder(
+ getOrganizerToken(), fragmentToken, ownerToken)
+ .setInitialBounds(bounds)
+ .setWindowingMode(windowingMode)
+ .build();
+ createTaskFragment(wct, fragmentOptions);
+ }
+
+ void createTaskFragment(@NonNull WindowContainerTransaction wct,
+ @NonNull TaskFragmentCreationParams fragmentOptions) {
+ if (mFragmentInfos.containsKey(fragmentOptions.getFragmentToken())) {
+ throw new IllegalArgumentException(
+ "There is an existing TaskFragment with fragmentToken="
+ + fragmentOptions.getFragmentToken());
+ }
wct.createTaskFragment(fragmentOptions);
}
@@ -189,18 +212,6 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
wct.reparentActivityToTaskFragment(fragmentToken, activity.getActivityToken());
}
- /**
- * @param ownerToken The token of the activity that creates this task fragment. It does not
- * have to be a child of this task fragment, but must belong to the same task.
- */
- private void createTaskFragmentAndStartActivity(@NonNull WindowContainerTransaction wct,
- @NonNull IBinder fragmentToken, @NonNull IBinder ownerToken, @NonNull Rect bounds,
- @WindowingMode int windowingMode, @NonNull Intent activityIntent,
- @Nullable Bundle activityOptions) {
- createTaskFragment(wct, fragmentToken, ownerToken, bounds, windowingMode);
- wct.startActivityInTaskFragment(fragmentToken, ownerToken, activityIntent, activityOptions);
- }
-
void setAdjacentTaskFragments(@NonNull WindowContainerTransaction wct,
@NonNull IBinder primary, @Nullable IBinder secondary, @Nullable SplitRule splitRule) {
WindowContainerTransaction.TaskFragmentAdjacentParams adjacentParams = null;
@@ -238,22 +249,6 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
wct.setCompanionTaskFragment(secondary, finishSecondaryWithPrimary ? primary : null);
}
- TaskFragmentCreationParams createFragmentOptions(@NonNull IBinder fragmentToken,
- @NonNull IBinder ownerToken, @NonNull Rect bounds, @WindowingMode int windowingMode) {
- if (mFragmentInfos.containsKey(fragmentToken)) {
- throw new IllegalArgumentException(
- "There is an existing TaskFragment with fragmentToken=" + fragmentToken);
- }
-
- return new TaskFragmentCreationParams.Builder(
- getOrganizerToken(),
- fragmentToken,
- ownerToken)
- .setInitialBounds(bounds)
- .setWindowingMode(windowingMode)
- .build();
- }
-
void resizeTaskFragment(@NonNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken,
@Nullable Rect bounds) {
if (!mFragmentInfos.containsKey(fragmentToken)) {
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 4df2d7d82f6a..d3dc05fb92d4 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -965,10 +965,16 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
@VisibleForTesting
@GuardedBy("mLock")
void onActivityDestroyed(@NonNull Activity activity) {
+ if (!activity.isFinishing()) {
+ // onDestroyed is triggered without finishing. This happens when the activity is
+ // relaunched. In this case, we don't want to cleanup the record.
+ return;
+ }
// Remove any pending appeared activity, as the server won't send finished activity to the
// organizer.
+ final IBinder activityToken = activity.getActivityToken();
for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
- mTaskContainers.valueAt(i).onActivityDestroyed(activity);
+ mTaskContainers.valueAt(i).onActivityDestroyed(activityToken);
}
// We didn't trigger the callback if there were any pending appeared activities, so check
// again after the pending is removed.
@@ -1170,16 +1176,33 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
* Returns a container that this activity is registered with. An activity can only belong to one
* container, or no container at all.
*/
+ @GuardedBy("mLock")
@Nullable
TaskFragmentContainer getContainerWithActivity(@NonNull Activity activity) {
- final IBinder activityToken = activity.getActivityToken();
+ return getContainerWithActivity(activity.getActivityToken());
+ }
+
+ @GuardedBy("mLock")
+ @Nullable
+ TaskFragmentContainer getContainerWithActivity(@NonNull IBinder activityToken) {
+ // Check pending appeared activity first because there can be a delay for the server
+ // update.
+ for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
+ final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i).mContainers;
+ for (int j = containers.size() - 1; j >= 0; j--) {
+ final TaskFragmentContainer container = containers.get(j);
+ if (container.hasPendingAppearedActivity(activityToken)) {
+ return container;
+ }
+ }
+ }
+
+ // Check appeared activity if there is no such pending appeared activity.
for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i).mContainers;
- // Traverse from top to bottom in case an activity is added to top pending, and hasn't
- // received update from server yet.
for (int j = containers.size() - 1; j >= 0; j--) {
final TaskFragmentContainer container = containers.get(j);
- if (container.hasActivity(activityToken)) {
+ if (container.hasAppearedActivity(activityToken)) {
return container;
}
}
@@ -1195,14 +1218,14 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
TaskFragmentContainer newContainer(@NonNull Activity pendingAppearedActivity,
@NonNull Activity activityInTask, int taskId) {
return newContainer(pendingAppearedActivity, null /* pendingAppearedIntent */,
- activityInTask, taskId);
+ activityInTask, taskId, null /* pairedPrimaryContainer */);
}
@GuardedBy("mLock")
TaskFragmentContainer newContainer(@NonNull Intent pendingAppearedIntent,
@NonNull Activity activityInTask, int taskId) {
return newContainer(null /* pendingAppearedActivity */, pendingAppearedIntent,
- activityInTask, taskId);
+ activityInTask, taskId, null /* pairedPrimaryContainer */);
}
/**
@@ -1214,10 +1237,13 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
* @param activityInTask activity in the same Task so that we can get the Task bounds
* if needed.
* @param taskId parent Task of the new TaskFragment.
+ * @param pairedPrimaryContainer the paired primary {@link TaskFragmentContainer}. When it is
+ * set, the new container will be added right above it.
*/
@GuardedBy("mLock")
TaskFragmentContainer newContainer(@Nullable Activity pendingAppearedActivity,
- @Nullable Intent pendingAppearedIntent, @NonNull Activity activityInTask, int taskId) {
+ @Nullable Intent pendingAppearedIntent, @NonNull Activity activityInTask, int taskId,
+ @Nullable TaskFragmentContainer pairedPrimaryContainer) {
if (activityInTask == null) {
throw new IllegalArgumentException("activityInTask must not be null,");
}
@@ -1226,7 +1252,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
}
final TaskContainer taskContainer = mTaskContainers.get(taskId);
final TaskFragmentContainer container = new TaskFragmentContainer(pendingAppearedActivity,
- pendingAppearedIntent, taskContainer, this);
+ pendingAppearedIntent, taskContainer, this, pairedPrimaryContainer);
return container;
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index 5395fb2ef5ed..47253d388f0d 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -36,6 +36,7 @@ import android.util.Size;
import android.view.View;
import android.view.WindowInsets;
import android.view.WindowMetrics;
+import android.window.TaskFragmentCreationParams;
import android.window.WindowContainerTransaction;
import androidx.annotation.GuardedBy;
@@ -307,10 +308,13 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
}
final int taskId = primaryContainer.getTaskId();
- final TaskFragmentContainer secondaryContainer = mController.newContainer(activityIntent,
- launchingActivity, taskId);
- final int windowingMode = mController.getTaskContainer(taskId)
- .getWindowingModeForSplitTaskFragment(primaryRectBounds);
+ final TaskFragmentContainer secondaryContainer = mController.newContainer(
+ null /* pendingAppearedActivity */, activityIntent, launchingActivity, taskId,
+ // Pass in the primary container to make sure it is added right above the primary.
+ primaryContainer);
+ final TaskContainer taskContainer = mController.getTaskContainer(taskId);
+ final int windowingMode = taskContainer.getWindowingModeForSplitTaskFragment(
+ primaryRectBounds);
mController.registerSplit(wct, primaryContainer, launchingActivity, secondaryContainer,
rule, splitAttributes);
startActivityToSide(wct, primaryContainer.getTaskFragmentToken(), primaryRectBounds,
@@ -412,17 +416,18 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
}
@Override
- void createTaskFragment(@NonNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken,
- @NonNull IBinder ownerToken, @NonNull Rect bounds, @WindowingMode int windowingMode) {
- final TaskFragmentContainer container = mController.getContainer(fragmentToken);
+ void createTaskFragment(@NonNull WindowContainerTransaction wct,
+ @NonNull TaskFragmentCreationParams fragmentOptions) {
+ final TaskFragmentContainer container = mController.getContainer(
+ fragmentOptions.getFragmentToken());
if (container == null) {
throw new IllegalStateException(
"Creating a task fragment that is not registered with controller.");
}
- container.setLastRequestedBounds(bounds);
- container.setLastRequestedWindowingMode(windowingMode);
- super.createTaskFragment(wct, fragmentToken, ownerToken, bounds, windowingMode);
+ container.setLastRequestedBounds(fragmentOptions.getInitialBounds());
+ container.setLastRequestedWindowingMode(fragmentOptions.getWindowingMode());
+ super.createTaskFragment(wct, fragmentOptions);
}
@Override
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
index dba5a7a1cf3c..03f4dc9c1167 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
@@ -166,16 +166,16 @@ class TaskContainer {
}
/** Called when the activity is destroyed. */
- void onActivityDestroyed(@NonNull Activity activity) {
+ void onActivityDestroyed(@NonNull IBinder activityToken) {
for (TaskFragmentContainer container : mContainers) {
- container.onActivityDestroyed(activity);
+ container.onActivityDestroyed(activityToken);
}
}
/** Removes the pending appeared activity from all TaskFragments in this Task. */
- void cleanupPendingAppearedActivity(@NonNull Activity pendingAppearedActivity) {
+ void cleanupPendingAppearedActivity(@NonNull IBinder activityToken) {
for (TaskFragmentContainer container : mContainers) {
- container.removePendingAppearedActivity(pendingAppearedActivity);
+ container.removePendingAppearedActivity(activityToken);
}
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java
index af5d8c561874..33220c44a3b5 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java
@@ -18,7 +18,9 @@ package androidx.window.extensions.embedding;
import static android.graphics.Matrix.MTRANS_X;
import static android.graphics.Matrix.MTRANS_Y;
+import static android.view.RemoteAnimationTarget.MODE_CLOSING;
+import android.graphics.Point;
import android.graphics.Rect;
import android.view.Choreographer;
import android.view.RemoteAnimationTarget;
@@ -49,6 +51,16 @@ class TaskFragmentAnimationAdapter {
/** Area in absolute coordinate that the animation surface shouldn't go beyond. */
@NonNull
private final Rect mWholeAnimationBounds = new Rect();
+ /**
+ * Area in absolute coordinate that should represent all the content to show for this window.
+ * This should be the end bounds for opening window, and start bounds for closing window in case
+ * the window is resizing during the open/close transition.
+ */
+ @NonNull
+ private final Rect mContentBounds = new Rect();
+ /** Offset relative to the window parent surface for {@link #mContentBounds}. */
+ @NonNull
+ private final Point mContentRelOffset = new Point();
@NonNull
final Transformation mTransformation = new Transformation();
@@ -78,6 +90,21 @@ class TaskFragmentAnimationAdapter {
mTarget = target;
mLeash = leash;
mWholeAnimationBounds.set(wholeAnimationBounds);
+ if (target.mode == MODE_CLOSING) {
+ // When it is closing, we want to show the content at the start position in case the
+ // window is resizing as well. For example, when the activities is changing from split
+ // to stack, the bottom TaskFragment will be resized to fullscreen when hiding.
+ final Rect startBounds = target.startBounds;
+ final Rect endBounds = target.screenSpaceBounds;
+ mContentBounds.set(startBounds);
+ mContentRelOffset.set(target.localBounds.left, target.localBounds.top);
+ mContentRelOffset.offset(
+ startBounds.left - endBounds.left,
+ startBounds.top - endBounds.top);
+ } else {
+ mContentBounds.set(target.screenSpaceBounds);
+ mContentRelOffset.set(target.localBounds.left, target.localBounds.top);
+ }
}
/**
@@ -108,8 +135,7 @@ class TaskFragmentAnimationAdapter {
/** To be overridden by subclasses to adjust the animation surface change. */
void onAnimationUpdateInner(@NonNull SurfaceControl.Transaction t) {
// Update the surface position and alpha.
- mTransformation.getMatrix().postTranslate(
- mTarget.localBounds.left, mTarget.localBounds.top);
+ mTransformation.getMatrix().postTranslate(mContentRelOffset.x, mContentRelOffset.y);
t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix);
t.setAlpha(mLeash, mTransformation.getAlpha());
@@ -117,9 +143,8 @@ class TaskFragmentAnimationAdapter {
// positionX/Y are in local coordinate, so minus the local offset to get the slide amount.
final int positionX = Math.round(mMatrix[MTRANS_X]);
final int positionY = Math.round(mMatrix[MTRANS_Y]);
- final Rect cropRect = new Rect(mTarget.screenSpaceBounds);
- final Rect localBounds = mTarget.localBounds;
- cropRect.offset(positionX - localBounds.left, positionY - localBounds.top);
+ final Rect cropRect = new Rect(mContentBounds);
+ cropRect.offset(positionX - mContentRelOffset.x, positionY - mContentRelOffset.y);
// Store the current offset of the surface top left from (0,0) in absolute coordinate.
final int offsetX = cropRect.left;
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java
index 0e13c59e593c..dcc12ac07589 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java
@@ -17,7 +17,9 @@
package androidx.window.extensions.embedding;
import static android.os.Process.THREAD_PRIORITY_DISPLAY;
+import static android.view.RemoteAnimationTarget.MODE_CHANGING;
import static android.view.RemoteAnimationTarget.MODE_CLOSING;
+import static android.view.RemoteAnimationTarget.MODE_OPENING;
import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_CLOSE;
import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN;
import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CHANGE;
@@ -252,9 +254,13 @@ class TaskFragmentAnimationRunner extends IRemoteAnimationRunner.Stub {
@NonNull
private List<TaskFragmentAnimationAdapter> createChangeAnimationAdapters(
@NonNull RemoteAnimationTarget[] targets) {
+ if (shouldUseJumpCutForChangeAnimation(targets)) {
+ return new ArrayList<>();
+ }
+
final List<TaskFragmentAnimationAdapter> adapters = new ArrayList<>();
for (RemoteAnimationTarget target : targets) {
- if (target.startBounds != null) {
+ if (target.mode == MODE_CHANGING) {
// This is the target with bounds change.
final Animation[] animations =
mAnimationSpec.createChangeBoundsChangeAnimations(target);
@@ -281,4 +287,24 @@ class TaskFragmentAnimationRunner extends IRemoteAnimationRunner.Stub {
}
return adapters;
}
+
+ /**
+ * Whether we should use jump cut for the change transition.
+ * This normally happens when opening a new secondary with the existing primary using a
+ * different split layout. This can be complicated, like from horizontal to vertical split with
+ * new split pairs.
+ * Uses a jump cut animation to simplify.
+ */
+ private boolean shouldUseJumpCutForChangeAnimation(@NonNull RemoteAnimationTarget[] targets) {
+ boolean hasOpeningWindow = false;
+ boolean hasClosingWindow = false;
+ for (RemoteAnimationTarget target : targets) {
+ if (target.hasAnimatingParent) {
+ continue;
+ }
+ hasOpeningWindow |= target.mode == MODE_OPENING;
+ hasClosingWindow |= target.mode == MODE_CLOSING;
+ }
+ return hasOpeningWindow && hasClosingWindow;
+ }
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java
index 13afa4910ae1..1f866c3b99c9 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java
@@ -114,8 +114,8 @@ class TaskFragmentAnimationSpec {
@NonNull
Animation createChangeBoundsCloseAnimation(@NonNull RemoteAnimationTarget target) {
final Rect parentBounds = target.taskInfo.configuration.windowConfiguration.getBounds();
- // TODO(b/258126915): we want to keep track of the closing start bounds
- final Rect bounds = target.screenSpaceBounds;
+ // Use startBounds if the window is closing in case it may also resize.
+ final Rect bounds = target.startBounds;
final int endTop;
final int endLeft;
if (parentBounds.top == bounds.top && parentBounds.bottom == bounds.bottom) {
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
index 71b884018bdb..fcf0ac78af38 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -43,6 +43,9 @@ import java.util.List;
* Client-side container for a stack of activities. Corresponds to an instance of TaskFragment
* on the server side.
*/
+// Suppress GuardedBy warning because all the TaskFragmentContainers are stored in
+// SplitController.mTaskContainers which is guarded.
+@SuppressWarnings("GuardedBy")
class TaskFragmentContainer {
private static final int APPEAR_EMPTY_TIMEOUT_MS = 3000;
@@ -66,11 +69,11 @@ class TaskFragmentContainer {
TaskFragmentInfo mInfo;
/**
- * Activities that are being reparented or being started to this container, but haven't been
- * added to {@link #mInfo} yet.
+ * Activity tokens that are being reparented or being started to this container, but haven't
+ * been added to {@link #mInfo} yet.
*/
@VisibleForTesting
- final ArrayList<Activity> mPendingAppearedActivities = new ArrayList<>();
+ final ArrayList<IBinder> mPendingAppearedActivities = new ArrayList<>();
/**
* When this container is created for an {@link Intent} to start within, we store that Intent
@@ -84,8 +87,11 @@ class TaskFragmentContainer {
private final List<TaskFragmentContainer> mContainersToFinishOnExit =
new ArrayList<>();
- /** Individual associated activities in different containers that should be finished on exit. */
- private final List<Activity> mActivitiesToFinishOnExit = new ArrayList<>();
+ /**
+ * Individual associated activity tokens in different containers that should be finished on
+ * exit.
+ */
+ private final List<IBinder> mActivitiesToFinishOnExit = new ArrayList<>();
/** Indicates whether the container was cleaned up after the last activity was removed. */
private boolean mIsFinished;
@@ -112,10 +118,12 @@ class TaskFragmentContainer {
/**
* Creates a container with an existing activity that will be re-parented to it in a window
* container transaction.
+ * @param pairedPrimaryContainer when it is set, the new container will be add right above it
*/
TaskFragmentContainer(@Nullable Activity pendingAppearedActivity,
@Nullable Intent pendingAppearedIntent, @NonNull TaskContainer taskContainer,
- @NonNull SplitController controller) {
+ @NonNull SplitController controller,
+ @Nullable TaskFragmentContainer pairedPrimaryContainer) {
if ((pendingAppearedActivity == null && pendingAppearedIntent == null)
|| (pendingAppearedActivity != null && pendingAppearedIntent != null)) {
throw new IllegalArgumentException(
@@ -124,7 +132,16 @@ class TaskFragmentContainer {
mController = controller;
mToken = new Binder("TaskFragmentContainer");
mTaskContainer = taskContainer;
- taskContainer.mContainers.add(this);
+ if (pairedPrimaryContainer != null) {
+ if (pairedPrimaryContainer.getTaskContainer() != taskContainer) {
+ throw new IllegalArgumentException(
+ "pairedPrimaryContainer must be in the same Task");
+ }
+ final int primaryIndex = taskContainer.mContainers.indexOf(pairedPrimaryContainer);
+ taskContainer.mContainers.add(primaryIndex + 1, this);
+ } else {
+ taskContainer.mContainers.add(this);
+ }
if (pendingAppearedActivity != null) {
addPendingAppearedActivity(pendingAppearedActivity);
}
@@ -158,8 +175,9 @@ class TaskFragmentContainer {
// in this intermediate state.
// Place those on top of the list since they will be on the top after reported from the
// server.
- for (Activity activity : mPendingAppearedActivities) {
- if (!activity.isFinishing()) {
+ for (IBinder token : mPendingAppearedActivities) {
+ final Activity activity = mController.getActivity(token);
+ if (activity != null && !activity.isFinishing()) {
allActivities.add(activity);
}
}
@@ -203,55 +221,58 @@ class TaskFragmentContainer {
/** Adds the activity that will be reparented to this container. */
void addPendingAppearedActivity(@NonNull Activity pendingAppearedActivity) {
- if (hasActivity(pendingAppearedActivity.getActivityToken())) {
+ final IBinder activityToken = pendingAppearedActivity.getActivityToken();
+ if (hasActivity(activityToken)) {
return;
}
- // Remove the pending activity from other TaskFragments.
- mTaskContainer.cleanupPendingAppearedActivity(pendingAppearedActivity);
- mPendingAppearedActivities.add(pendingAppearedActivity);
- updateActivityClientRecordTaskFragmentToken(pendingAppearedActivity);
+ // Remove the pending activity from other TaskFragments in case the activity is reparented
+ // again before the server update.
+ mTaskContainer.cleanupPendingAppearedActivity(activityToken);
+ mPendingAppearedActivities.add(activityToken);
+ updateActivityClientRecordTaskFragmentToken(activityToken);
}
/**
* Updates the {@link ActivityThread.ActivityClientRecord#mTaskFragmentToken} for the
* activity. This makes sure the token is up-to-date if the activity is relaunched later.
*/
- private void updateActivityClientRecordTaskFragmentToken(@NonNull Activity activity) {
+ private void updateActivityClientRecordTaskFragmentToken(@NonNull IBinder activityToken) {
final ActivityThread.ActivityClientRecord record = ActivityThread
- .currentActivityThread().getActivityClient(activity.getActivityToken());
+ .currentActivityThread().getActivityClient(activityToken);
if (record != null) {
record.mTaskFragmentToken = mToken;
}
}
- void removePendingAppearedActivity(@NonNull Activity pendingAppearedActivity) {
- mPendingAppearedActivities.remove(pendingAppearedActivity);
+ void removePendingAppearedActivity(@NonNull IBinder activityToken) {
+ mPendingAppearedActivities.remove(activityToken);
}
void clearPendingAppearedActivities() {
- final List<Activity> cleanupActivities = new ArrayList<>(mPendingAppearedActivities);
+ final List<IBinder> cleanupActivities = new ArrayList<>(mPendingAppearedActivities);
// Clear mPendingAppearedActivities so that #getContainerWithActivity won't return the
// current TaskFragment.
mPendingAppearedActivities.clear();
mPendingAppearedIntent = null;
// For removed pending activities, we need to update the them to their previous containers.
- for (Activity activity : cleanupActivities) {
+ for (IBinder activityToken : cleanupActivities) {
final TaskFragmentContainer curContainer = mController.getContainerWithActivity(
- activity);
+ activityToken);
if (curContainer != null) {
- curContainer.updateActivityClientRecordTaskFragmentToken(activity);
+ curContainer.updateActivityClientRecordTaskFragmentToken(activityToken);
}
}
}
/** Called when the activity is destroyed. */
- void onActivityDestroyed(@NonNull Activity activity) {
- removePendingAppearedActivity(activity);
+ void onActivityDestroyed(@NonNull IBinder activityToken) {
+ removePendingAppearedActivity(activityToken);
if (mInfo != null) {
// Remove the activity now because there can be a delay before the server callback.
- mInfo.getActivities().remove(activity.getActivityToken());
+ mInfo.getActivities().remove(activityToken);
}
+ mActivitiesToFinishOnExit.remove(activityToken);
}
@Nullable
@@ -275,16 +296,24 @@ class TaskFragmentContainer {
mPendingAppearedIntent = null;
}
- boolean hasActivity(@NonNull IBinder token) {
- if (mInfo != null && mInfo.getActivities().contains(token)) {
- return true;
- }
- for (Activity activity : mPendingAppearedActivities) {
- if (activity.getActivityToken().equals(token)) {
- return true;
- }
- }
- return false;
+ boolean hasActivity(@NonNull IBinder activityToken) {
+ // Instead of using (hasAppearedActivity() || hasPendingAppearedActivity), we want to make
+ // sure the controller considers this container as the one containing the activity.
+ // This is needed when the activity is added as pending appeared activity to one
+ // TaskFragment while it is also an appeared activity in another.
+ return mController.getContainerWithActivity(activityToken) == this;
+ }
+
+ /** Whether this activity has appeared in the TaskFragment on the server side. */
+ boolean hasAppearedActivity(@NonNull IBinder activityToken) {
+ return mInfo != null && mInfo.getActivities().contains(activityToken);
+ }
+
+ /**
+ * Whether we are waiting for this activity to appear in the TaskFragment on the server side.
+ */
+ boolean hasPendingAppearedActivity(@NonNull IBinder activityToken) {
+ return mPendingAppearedActivities.contains(activityToken);
}
int getRunningActivityCount() {
@@ -342,8 +371,8 @@ class TaskFragmentContainer {
// Cleanup activities that were being re-parented
List<IBinder> infoActivities = mInfo.getActivities();
for (int i = mPendingAppearedActivities.size() - 1; i >= 0; --i) {
- final Activity activity = mPendingAppearedActivities.get(i);
- if (infoActivities.contains(activity.getActivityToken())) {
+ final IBinder activityToken = mPendingAppearedActivities.get(i);
+ if (infoActivities.contains(activityToken)) {
mPendingAppearedActivities.remove(i);
}
}
@@ -392,7 +421,7 @@ class TaskFragmentContainer {
if (mIsFinished) {
return;
}
- mActivitiesToFinishOnExit.add(activityToFinish);
+ mActivitiesToFinishOnExit.add(activityToFinish.getActivityToken());
}
/**
@@ -402,7 +431,7 @@ class TaskFragmentContainer {
if (mIsFinished) {
return;
}
- mActivitiesToFinishOnExit.remove(activityToRemove);
+ mActivitiesToFinishOnExit.remove(activityToRemove.getActivityToken());
}
/** Removes all dependencies that should be finished when this container is finished. */
@@ -470,8 +499,9 @@ class TaskFragmentContainer {
mContainersToFinishOnExit.clear();
// Finish associated activities
- for (Activity activity : mActivitiesToFinishOnExit) {
- if (activity.isFinishing()
+ for (IBinder activityToken : mActivitiesToFinishOnExit) {
+ final Activity activity = mController.getActivity(activityToken);
+ if (activity == null || activity.isFinishing()
|| controller.shouldRetainAssociatedActivity(this, activity)) {
continue;
}
@@ -540,7 +570,8 @@ class TaskFragmentContainer {
}
int maxMinWidth = mInfo.getMinimumWidth();
int maxMinHeight = mInfo.getMinimumHeight();
- for (Activity activity : mPendingAppearedActivities) {
+ for (IBinder activityToken : mPendingAppearedActivities) {
+ final Activity activity = mController.getActivity(activityToken);
final Size minDimensions = SplitPresenter.getMinDimensions(activity);
if (minDimensions == null) {
continue;
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java
index 92011af27619..bc03e4ec303d 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java
@@ -21,6 +21,7 @@ import static android.view.Display.DEFAULT_DISPLAY;
import static androidx.window.extensions.embedding.SplitRule.FINISH_ALWAYS;
import static androidx.window.extensions.embedding.SplitRule.FINISH_NEVER;
+import static org.junit.Assert.assertFalse;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
@@ -45,6 +46,8 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+// Suppress GuardedBy warning on unit tests
+@SuppressWarnings("GuardedBy")
public class EmbeddingTestUtils {
static final Rect TASK_BOUNDS = new Rect(0, 0, 600, 1200);
static final int TASK_ID = 10;
@@ -191,6 +194,15 @@ public class EmbeddingTestUtils {
return new TaskContainer(TASK_ID, activity);
}
+ static TaskContainer createTestTaskContainer(@NonNull SplitController controller) {
+ final TaskContainer taskContainer = createTestTaskContainer();
+ final int taskId = taskContainer.getTaskId();
+ // Should not call to create TaskContainer with the same task id twice.
+ assertFalse(controller.mTaskContainers.contains(taskId));
+ controller.mTaskContainers.put(taskId, taskContainer);
+ return taskContainer;
+ }
+
static WindowLayoutInfo createWindowLayoutInfo() {
final FoldingFeature foldingFeature = new FoldingFeature(
new Rect(
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
index 31aa09c902b1..bbb454d31c38 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
@@ -102,7 +102,7 @@ public class JetpackTaskFragmentOrganizerTest {
public void testExpandTaskFragment() {
final TaskContainer taskContainer = createTestTaskContainer();
final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
- new Intent(), taskContainer, mSplitController);
+ new Intent(), taskContainer, mSplitController, null /* pairedPrimaryContainer */);
final TaskFragmentInfo info = createMockInfo(container);
mOrganizer.mFragmentInfos.put(container.getTaskFragmentToken(), info);
container.setInfo(mTransaction, info);
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
index 8c1b87a650e0..6725dfdb94e8 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
@@ -169,7 +169,7 @@ public class SplitControllerTest {
final TaskContainer taskContainer = createTestTaskContainer();
// tf1 has no running activity so is not active.
final TaskFragmentContainer tf1 = new TaskFragmentContainer(null /* activity */,
- new Intent(), taskContainer, mSplitController);
+ new Intent(), taskContainer, mSplitController, null /* pairedPrimaryContainer */);
// tf2 has running activity so is active.
final TaskFragmentContainer tf2 = mock(TaskFragmentContainer.class);
doReturn(1).when(tf2).getRunningActivityCount();
@@ -242,6 +242,14 @@ public class SplitControllerTest {
assertTrue(tf.hasActivity(mActivity.getActivityToken()));
+ // When the activity is not finishing, do not clear the record.
+ doReturn(false).when(mActivity).isFinishing();
+ mSplitController.onActivityDestroyed(mActivity);
+
+ assertTrue(tf.hasActivity(mActivity.getActivityToken()));
+
+ // Clear the record when the activity is finishing and destroyed.
+ doReturn(true).when(mActivity).isFinishing();
mSplitController.onActivityDestroyed(mActivity);
assertFalse(tf.hasActivity(mActivity.getActivityToken()));
@@ -367,7 +375,7 @@ public class SplitControllerTest {
final Intent intent = new Intent();
final TaskContainer taskContainer = createTestTaskContainer();
final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
- intent, taskContainer, mSplitController);
+ intent, taskContainer, mSplitController, null /* pairedPrimaryContainer */);
final SplitController.ActivityStartMonitor monitor =
mSplitController.getActivityStartMonitor();
@@ -601,7 +609,7 @@ public class SplitControllerTest {
false /* isOnReparent */);
assertFalse(result);
- verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt());
+ verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any());
}
@Test
@@ -763,7 +771,7 @@ public class SplitControllerTest {
false /* isOnReparent */);
assertTrue(result);
- verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt());
+ verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any());
verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any(), any());
}
@@ -805,7 +813,7 @@ public class SplitControllerTest {
false /* isOnReparent */);
assertTrue(result);
- verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt());
+ verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any());
verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any(), any());
}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java
index 95328ce700e3..13e709271221 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java
@@ -122,7 +122,7 @@ public class TaskContainerTest {
assertTrue(taskContainer.isEmpty());
final TaskFragmentContainer tf = new TaskFragmentContainer(null /* activity */,
- new Intent(), taskContainer, mController);
+ new Intent(), taskContainer, mController, null /* pairedPrimaryContainer */);
assertFalse(taskContainer.isEmpty());
@@ -138,11 +138,11 @@ public class TaskContainerTest {
assertNull(taskContainer.getTopTaskFragmentContainer());
final TaskFragmentContainer tf0 = new TaskFragmentContainer(null /* activity */,
- new Intent(), taskContainer, mController);
+ new Intent(), taskContainer, mController, null /* pairedPrimaryContainer */);
assertEquals(tf0, taskContainer.getTopTaskFragmentContainer());
final TaskFragmentContainer tf1 = new TaskFragmentContainer(null /* activity */,
- new Intent(), taskContainer, mController);
+ new Intent(), taskContainer, mController, null /* pairedPrimaryContainer */);
assertEquals(tf1, taskContainer.getTopTaskFragmentContainer());
}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
index d43c471fb8ae..5c3ba72e2361 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
@@ -94,18 +94,21 @@ public class TaskFragmentContainerTest {
// One of the activity and the intent must be non-null
assertThrows(IllegalArgumentException.class,
- () -> new TaskFragmentContainer(null, null, taskContainer, mController));
+ () -> new TaskFragmentContainer(null, null, taskContainer, mController,
+ null /* pairedPrimaryContainer */));
// One of the activity and the intent must be null.
assertThrows(IllegalArgumentException.class,
- () -> new TaskFragmentContainer(mActivity, mIntent, taskContainer, mController));
+ () -> new TaskFragmentContainer(mActivity, mIntent, taskContainer, mController,
+ null /* pairedPrimaryContainer */));
}
@Test
public void testFinish() {
final TaskContainer taskContainer = createTestTaskContainer();
final TaskFragmentContainer container = new TaskFragmentContainer(mActivity,
- null /* pendingAppearedIntent */, taskContainer, mController);
+ null /* pendingAppearedIntent */, taskContainer, mController,
+ null /* pairedPrimaryContainer */);
doReturn(container).when(mController).getContainerWithActivity(mActivity);
// Only remove the activity, but not clear the reference until appeared.
@@ -137,12 +140,14 @@ public class TaskFragmentContainerTest {
public void testFinish_notFinishActivityThatIsReparenting() {
final TaskContainer taskContainer = createTestTaskContainer();
final TaskFragmentContainer container0 = new TaskFragmentContainer(mActivity,
- null /* pendingAppearedIntent */, taskContainer, mController);
+ null /* pendingAppearedIntent */, taskContainer, mController,
+ null /* pairedPrimaryContainer */);
final TaskFragmentInfo info = createMockTaskFragmentInfo(container0, mActivity);
container0.setInfo(mTransaction, info);
// Request to reparent the activity to a new TaskFragment.
final TaskFragmentContainer container1 = new TaskFragmentContainer(mActivity,
- null /* pendingAppearedIntent */, taskContainer, mController);
+ null /* pendingAppearedIntent */, taskContainer, mController,
+ null /* pairedPrimaryContainer */);
doReturn(container1).when(mController).getContainerWithActivity(mActivity);
final WindowContainerTransaction wct = new WindowContainerTransaction();
@@ -159,9 +164,11 @@ public class TaskFragmentContainerTest {
final TaskContainer taskContainer = createTestTaskContainer();
// Pending activity should be cleared when it has appeared on server side.
final TaskFragmentContainer pendingActivityContainer = new TaskFragmentContainer(mActivity,
- null /* pendingAppearedIntent */, taskContainer, mController);
+ null /* pendingAppearedIntent */, taskContainer, mController,
+ null /* pairedPrimaryContainer */);
- assertTrue(pendingActivityContainer.mPendingAppearedActivities.contains(mActivity));
+ assertTrue(pendingActivityContainer.mPendingAppearedActivities.contains(
+ mActivity.getActivityToken()));
final TaskFragmentInfo info0 = createMockTaskFragmentInfo(pendingActivityContainer,
mActivity);
@@ -171,7 +178,8 @@ public class TaskFragmentContainerTest {
// Pending intent should be cleared when the container becomes non-empty.
final TaskFragmentContainer pendingIntentContainer = new TaskFragmentContainer(
- null /* pendingAppearedActivity */, mIntent, taskContainer, mController);
+ null /* pendingAppearedActivity */, mIntent, taskContainer, mController,
+ null /* pairedPrimaryContainer */);
assertEquals(mIntent, pendingIntentContainer.getPendingAppearedIntent());
@@ -186,7 +194,7 @@ public class TaskFragmentContainerTest {
public void testIsWaitingActivityAppear() {
final TaskContainer taskContainer = createTestTaskContainer();
final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
- mIntent, taskContainer, mController);
+ mIntent, taskContainer, mController, null /* pairedPrimaryContainer */);
assertTrue(container.isWaitingActivityAppear());
@@ -208,7 +216,7 @@ public class TaskFragmentContainerTest {
doNothing().when(mController).onTaskFragmentAppearEmptyTimeout(any(), any());
final TaskContainer taskContainer = createTestTaskContainer();
final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
- mIntent, taskContainer, mController);
+ mIntent, taskContainer, mController, null /* pairedPrimaryContainer */);
assertNull(container.mAppearEmptyTimeout);
@@ -248,7 +256,7 @@ public class TaskFragmentContainerTest {
public void testCollectNonFinishingActivities() {
final TaskContainer taskContainer = createTestTaskContainer();
final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
- mIntent, taskContainer, mController);
+ mIntent, taskContainer, mController, null /* pairedPrimaryContainer */);
List<Activity> activities = container.collectNonFinishingActivities();
assertTrue(activities.isEmpty());
@@ -276,7 +284,7 @@ public class TaskFragmentContainerTest {
public void testAddPendingActivity() {
final TaskContainer taskContainer = createTestTaskContainer();
final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
- mIntent, taskContainer, mController);
+ mIntent, taskContainer, mController, null /* pairedPrimaryContainer */);
container.addPendingAppearedActivity(mActivity);
assertEquals(1, container.collectNonFinishingActivities().size());
@@ -290,9 +298,9 @@ public class TaskFragmentContainerTest {
public void testIsAbove() {
final TaskContainer taskContainer = createTestTaskContainer();
final TaskFragmentContainer container0 = new TaskFragmentContainer(null /* activity */,
- mIntent, taskContainer, mController);
+ mIntent, taskContainer, mController, null /* pairedPrimaryContainer */);
final TaskFragmentContainer container1 = new TaskFragmentContainer(null /* activity */,
- mIntent, taskContainer, mController);
+ mIntent, taskContainer, mController, null /* pairedPrimaryContainer */);
assertTrue(container1.isAbove(container0));
assertFalse(container0.isAbove(container1));
@@ -302,7 +310,7 @@ public class TaskFragmentContainerTest {
public void testGetBottomMostActivity() {
final TaskContainer taskContainer = createTestTaskContainer();
final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
- mIntent, taskContainer, mController);
+ mIntent, taskContainer, mController, null /* pairedPrimaryContainer */);
container.addPendingAppearedActivity(mActivity);
assertEquals(mActivity, container.getBottomMostActivity());
@@ -317,9 +325,9 @@ public class TaskFragmentContainerTest {
@Test
public void testOnActivityDestroyed() {
- final TaskContainer taskContainer = createTestTaskContainer();
+ final TaskContainer taskContainer = createTestTaskContainer(mController);
final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
- mIntent, taskContainer, mController);
+ mIntent, taskContainer, mController, null /* pairedPrimaryContainer */);
container.addPendingAppearedActivity(mActivity);
final List<IBinder> activities = new ArrayList<>();
activities.add(mActivity.getActivityToken());
@@ -328,7 +336,7 @@ public class TaskFragmentContainerTest {
assertTrue(container.hasActivity(mActivity.getActivityToken()));
- taskContainer.onActivityDestroyed(mActivity);
+ taskContainer.onActivityDestroyed(mActivity.getActivityToken());
// It should not contain the destroyed Activity.
assertFalse(container.hasActivity(mActivity.getActivityToken()));
@@ -339,7 +347,7 @@ public class TaskFragmentContainerTest {
// True if no info set.
final TaskContainer taskContainer = createTestTaskContainer();
final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
- mIntent, taskContainer, mController);
+ mIntent, taskContainer, mController, null /* pairedPrimaryContainer */);
spyOn(taskContainer);
doReturn(true).when(taskContainer).isVisible();
@@ -398,6 +406,100 @@ public class TaskFragmentContainerTest {
assertFalse(taskContainer.isInIntermediateState());
}
+ @Test
+ public void testHasAppearedActivity() {
+ final TaskContainer taskContainer = createTestTaskContainer();
+ final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
+ mIntent, taskContainer, mController, null /* pairedPrimaryContainer */);
+ container.addPendingAppearedActivity(mActivity);
+
+ assertFalse(container.hasAppearedActivity(mActivity.getActivityToken()));
+
+ final List<IBinder> activities = new ArrayList<>();
+ activities.add(mActivity.getActivityToken());
+ doReturn(activities).when(mInfo).getActivities();
+ container.setInfo(mTransaction, mInfo);
+
+ assertTrue(container.hasAppearedActivity(mActivity.getActivityToken()));
+ }
+
+ @Test
+ public void testHasPendingAppearedActivity() {
+ final TaskContainer taskContainer = createTestTaskContainer();
+ final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
+ mIntent, taskContainer, mController, null /* pairedPrimaryContainer */);
+ container.addPendingAppearedActivity(mActivity);
+
+ assertTrue(container.hasPendingAppearedActivity(mActivity.getActivityToken()));
+
+ final List<IBinder> activities = new ArrayList<>();
+ activities.add(mActivity.getActivityToken());
+ doReturn(activities).when(mInfo).getActivities();
+ container.setInfo(mTransaction, mInfo);
+
+ assertFalse(container.hasPendingAppearedActivity(mActivity.getActivityToken()));
+ }
+
+ @Test
+ public void testHasActivity() {
+ final TaskContainer taskContainer = createTestTaskContainer(mController);
+ final TaskFragmentContainer container1 = new TaskFragmentContainer(null /* activity */,
+ mIntent, taskContainer, mController, null /* pairedPrimaryContainer */);
+ final TaskFragmentContainer container2 = new TaskFragmentContainer(null /* activity */,
+ mIntent, taskContainer, mController, null /* pairedPrimaryContainer */);
+
+ // Activity is pending appeared on container2.
+ container2.addPendingAppearedActivity(mActivity);
+
+ assertFalse(container1.hasActivity(mActivity.getActivityToken()));
+ assertTrue(container2.hasActivity(mActivity.getActivityToken()));
+
+ // Activity is pending appeared on container1 (removed from container2).
+ container1.addPendingAppearedActivity(mActivity);
+
+ assertTrue(container1.hasActivity(mActivity.getActivityToken()));
+ assertFalse(container2.hasActivity(mActivity.getActivityToken()));
+
+ final List<IBinder> activities = new ArrayList<>();
+ activities.add(mActivity.getActivityToken());
+ doReturn(activities).when(mInfo).getActivities();
+
+ // Although Activity is appeared on container2, we prioritize pending appeared record on
+ // container1.
+ container2.setInfo(mTransaction, mInfo);
+
+ assertTrue(container1.hasActivity(mActivity.getActivityToken()));
+ assertFalse(container2.hasActivity(mActivity.getActivityToken()));
+
+ // When the pending appeared record is removed from container1, we respect the appeared
+ // record in container2.
+ container1.removePendingAppearedActivity(mActivity.getActivityToken());
+
+ assertFalse(container1.hasActivity(mActivity.getActivityToken()));
+ assertTrue(container2.hasActivity(mActivity.getActivityToken()));
+ }
+
+ @Test
+ public void testNewContainerWithPairedPrimaryContainer() {
+ final TaskContainer taskContainer = createTestTaskContainer();
+ final TaskFragmentContainer tf0 = new TaskFragmentContainer(
+ null /* pendingAppearedActivity */, new Intent(), taskContainer, mController,
+ null /* pairedPrimaryTaskFragment */);
+ final TaskFragmentContainer tf1 = new TaskFragmentContainer(
+ null /* pendingAppearedActivity */, new Intent(), taskContainer, mController,
+ null /* pairedPrimaryTaskFragment */);
+ taskContainer.mContainers.add(tf0);
+ taskContainer.mContainers.add(tf1);
+
+ // When tf2 is created with using tf0 as pairedPrimaryContainer, tf2 should be inserted
+ // right above tf0.
+ final TaskFragmentContainer tf2 = new TaskFragmentContainer(
+ null /* pendingAppearedActivity */, new Intent(), taskContainer, mController, tf0);
+ assertEquals(0, taskContainer.indexOf(tf0));
+ assertEquals(1, taskContainer.indexOf(tf2));
+ assertEquals(2, taskContainer.indexOf(tf1));
+ }
+
/** Creates a mock activity in the organizer process. */
private Activity createMockActivity() {
final Activity activity = mock(Activity.class);
diff --git a/libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml b/libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml
index e6ae28207970..29945937788b 100644
--- a/libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml
+++ b/libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml
@@ -1,19 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- Copyright (C) 2019 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
@@ -25,13 +25,12 @@
android:fillAlpha="0.8"
android:pathData="M0,24 a24,24 0 1,0 48,0 a24,24 0 1,0 -48,0"/>
<group
- android:translateX="12"
- android:translateY="12">
+ android:scaleX="0.8"
+ android:scaleY="0.8"
+ android:translateX="10"
+ android:translateY="10">
<path
- android:fillColor="@color/compat_controls_text"
- android:pathData="M6,13c0,-1.65 0.67,-3.15 1.76,-4.24L6.34,7.34C4.9,8.79 4,10.79 4,13c0,4.08 3.05,7.44 7,7.93v-2.02C8.17,18.43 6,15.97 6,13z"/>
- <path
- android:fillColor="@color/compat_controls_text"
- android:pathData="M20,13c0,-4.42 -3.58,-8 -8,-8c-0.06,0 -0.12,0.01 -0.18,0.01v0l1.09,-1.09L11.5,2.5L8,6l3.5,3.5l1.41,-1.41l-1.08,-1.08C11.89,7.01 11.95,7 12,7c3.31,0 6,2.69 6,6c0,2.97 -2.17,5.43 -5,5.91v2.02C16.95,20.44 20,17.08 20,13z"/>
+ android:pathData="M0,36V24.5H3V30.85L10.4,23.45L12.55,25.6L5.15,33H11.5V36H0ZM24.5,36V33H30.85L23.5,25.65L25.65,23.5L33,30.85V24.5H36V36H24.5ZM10.35,12.5L3,5.15V11.5H0V0H11.5V3H5.15L12.5,10.35L10.35,12.5ZM25.65,12.5L23.5,10.35L30.85,3H24.5V0H36V11.5H33V5.15L25.65,12.5Z"
+ android:fillColor="@color/compat_controls_text"/>
</group>
</vector>
diff --git a/libs/WindowManager/Shell/res/values-af/strings.xml b/libs/WindowManager/Shell/res/values-af/strings.xml
index 27c245c12f3e..904ae860d76d 100644
--- a/libs/WindowManager/Shell/res/values-af/strings.xml
+++ b/libs/WindowManager/Shell/res/values-af/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Laat los"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Program sal dalk nie met verdeelde skerm werk nie."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Program steun nie verdeelde skerm nie."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Hierdie app kan net in 1 venster oopgemaak word."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Program sal dalk nie op \'n sekondêre skerm werk nie."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Program steun nie begin op sekondêre skerms nie."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Skermverdeler"</string>
diff --git a/libs/WindowManager/Shell/res/values-am/strings.xml b/libs/WindowManager/Shell/res/values-am/strings.xml
index 0248719e3036..51de2e57b6b0 100644
--- a/libs/WindowManager/Shell/res/values-am/strings.xml
+++ b/libs/WindowManager/Shell/res/values-am/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"መተግበሪያ ከተከፈለ ማያ ገጽ ጋር ላይሠራ ይችላል"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"መተግበሪያው የተከፈለ ማያ ገጽን አይደግፍም።"</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"ይህ መተግበሪያ መከፈት የሚችለው በ1 መስኮት ብቻ ነው።"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"መተግበሪያ በሁለተኛ ማሳያ ላይ ላይሠራ ይችላል።"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"መተግበሪያ በሁለተኛ ማሳያዎች ላይ ማስጀመርን አይደግፍም።"</string>
<string name="accessibility_divider" msgid="703810061635792791">"የተከፈለ የማያ ገጽ ከፋይ"</string>
diff --git a/libs/WindowManager/Shell/res/values-ar/strings.xml b/libs/WindowManager/Shell/res/values-ar/strings.xml
index cc7df4a35bdf..635334db0d64 100644
--- a/libs/WindowManager/Shell/res/values-ar/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ar/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"إظهار"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"قد لا يعمل التطبيق بشكل سليم في وضع \"تقسيم الشاشة\"."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"التطبيق لا يتيح تقسيم الشاشة."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"لا يمكن فتح هذا التطبيق إلا في نافذة واحدة."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"قد لا يعمل التطبيق على شاشة عرض ثانوية."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"لا يمكن تشغيل التطبيق على شاشات عرض ثانوية."</string>
<string name="accessibility_divider" msgid="703810061635792791">"أداة تقسيم الشاشة"</string>
diff --git a/libs/WindowManager/Shell/res/values-as/strings.xml b/libs/WindowManager/Shell/res/values-as/strings.xml
index aafcfe7aa145..788fd5c0597a 100644
--- a/libs/WindowManager/Shell/res/values-as/strings.xml
+++ b/libs/WindowManager/Shell/res/values-as/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"দেখুৱাওক"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"এপ্‌টোৱে বিভাজিত স্ক্ৰীনৰ সৈতে কাম নকৰিব পাৰে।"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"এপ্‌টোৱে বিভাজিত স্ক্ৰীন সমৰ্থন নকৰে।"</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"এই এপ্‌টো কেৱল ১ খন ৱিণ্ড’ত খুলিব পাৰি।"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"গৌণ ডিছপ্লেত এপে সঠিকভাৱে কাম নকৰিব পাৰে।"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"গৌণ ডিছপ্লেত এপ্ লঞ্চ কৰিব নোৱাৰি।"</string>
<string name="accessibility_divider" msgid="703810061635792791">"স্প্লিট স্ক্ৰীনৰ বিভাজক"</string>
diff --git a/libs/WindowManager/Shell/res/values-az/strings.xml b/libs/WindowManager/Shell/res/values-az/strings.xml
index d4b5bad840cf..a56918d98592 100644
--- a/libs/WindowManager/Shell/res/values-az/strings.xml
+++ b/libs/WindowManager/Shell/res/values-az/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Güvənli məkandan çıxarın"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Tətbiq bölünmüş ekran ilə işləməyə bilər."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Tətbiq ekran bölünməsini dəstəkləmir."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Bu tətbiq yalnız 1 pəncərədə açıla bilər."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Tətbiq ikinci ekranda işləməyə bilər."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Tətbiq ikinci ekranda başlamağı dəstəkləmir."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Bölünmüş ekran ayırıcısı"</string>
diff --git a/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml b/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml
index c2ee13bd14d5..dcb03aa0b365 100644
--- a/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Uklonite iz tajne memorije"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Aplikacija možda neće raditi sa podeljenim ekranom."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplikacija ne podržava podeljeni ekran."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Ova aplikacija može da se otvori samo u jednom prozoru."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikacija možda neće funkcionisati na sekundarnom ekranu."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikacija ne podržava pokretanje na sekundarnim ekranima."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Razdelnik podeljenog ekrana"</string>
diff --git a/libs/WindowManager/Shell/res/values-be/strings.xml b/libs/WindowManager/Shell/res/values-be/strings.xml
index ea205efe20a8..f6b285a47f5a 100644
--- a/libs/WindowManager/Shell/res/values-be/strings.xml
+++ b/libs/WindowManager/Shell/res/values-be/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Паказаць"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Праграма можа не працаваць у рэжыме падзеленага экрана."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Праграма не падтрымлівае функцыю дзялення экрана."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Гэту праграму можна адкрыць толькі ў адным акне."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Праграма можа не працаваць на дадатковых экранах."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Праграма не падтрымлівае запуск на дадатковых экранах."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Раздзяляльнік падзеленага экрана"</string>
diff --git a/libs/WindowManager/Shell/res/values-bg/strings.xml b/libs/WindowManager/Shell/res/values-bg/strings.xml
index f91dda0e3c04..044f2a7438dd 100644
--- a/libs/WindowManager/Shell/res/values-bg/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bg/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Отмяна на съхраняването"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Приложението може да не работи в режим на разделен екран."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Приложението не поддържа разделен екран."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Това приложение може да се отвори само в 1 прозорец."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Възможно е приложението да не работи на алтернативни дисплеи."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Приложението не поддържа използването на алтернативни дисплеи."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Разделител в режима за разделен екран"</string>
diff --git a/libs/WindowManager/Shell/res/values-bn/strings.xml b/libs/WindowManager/Shell/res/values-bn/strings.xml
index 0cc798ca548d..1fb0292bc56d 100644
--- a/libs/WindowManager/Shell/res/values-bn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bn/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"আনস্ট্যাস করুন"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"অ্যাপটি স্প্লিট স্ক্রিনে কাজ নাও করতে পারে।"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"অ্যাপ্লিকেশান বিভক্ত-স্ক্রিন সমর্থন করে না৷"</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"এই অ্যাপটি শুধু ১টি উইন্ডোয় খোলা যেতে পারে।"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"সেকেন্ডারি ডিসপ্লেতে অ্যাপটি কাজ নাও করতে পারে।"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"সেকেন্ডারি ডিসপ্লেতে অ্যাপ লঞ্চ করা যাবে না।"</string>
<string name="accessibility_divider" msgid="703810061635792791">"বিভক্ত-স্ক্রিন বিভাজক"</string>
diff --git a/libs/WindowManager/Shell/res/values-bs/strings.xml b/libs/WindowManager/Shell/res/values-bs/strings.xml
index e235179f5472..8e52d78f2f5f 100644
--- a/libs/WindowManager/Shell/res/values-bs/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bs/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Vađenje iz stasha"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Aplikacija možda neće raditi na podijeljenom ekranu."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplikacija ne podržava dijeljenje ekrana."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Ova aplikacija se može otvoriti samo u 1 prozoru."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikacija možda neće raditi na sekundarnom ekranu."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikacija ne podržava pokretanje na sekundarnim ekranima."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Razdjelnik podijeljenog ekrana"</string>
diff --git a/libs/WindowManager/Shell/res/values-ca/strings.xml b/libs/WindowManager/Shell/res/values-ca/strings.xml
index 30b8d090291c..6bc4f99ee7ef 100644
--- a/libs/WindowManager/Shell/res/values-ca/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ca/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Deixa d\'amagar"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"És possible que l\'aplicació no funcioni amb la pantalla dividida."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"L\'aplicació no admet la pantalla dividida."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Aquesta aplicació només pot obrir-se en 1 finestra."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"És possible que l\'aplicació no funcioni en una pantalla secundària."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"L\'aplicació no es pot obrir en pantalles secundàries."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Divisor de pantalles"</string>
diff --git a/libs/WindowManager/Shell/res/values-cs/strings.xml b/libs/WindowManager/Shell/res/values-cs/strings.xml
index b78e93adaf00..b638b0e5179f 100644
--- a/libs/WindowManager/Shell/res/values-cs/strings.xml
+++ b/libs/WindowManager/Shell/res/values-cs/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Zrušit uložení"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Aplikace v režimu rozdělené obrazovky nemusí fungovat."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplikace nepodporuje režim rozdělené obrazovky."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Tuto aplikaci lze otevřít jen na jednom okně."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikace na sekundárním displeji nemusí fungovat."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikace nepodporuje spuštění na sekundárních displejích."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Čára rozdělující obrazovku"</string>
diff --git a/libs/WindowManager/Shell/res/values-da/strings.xml b/libs/WindowManager/Shell/res/values-da/strings.xml
index 1544099dd5aa..e0b7a8cb44d7 100644
--- a/libs/WindowManager/Shell/res/values-da/strings.xml
+++ b/libs/WindowManager/Shell/res/values-da/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Vis"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Appen fungerer muligvis ikke i opdelt skærm."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Appen understøtter ikke opdelt skærm."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Denne app kan kun åbnes i 1 vindue."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Appen fungerer muligvis ikke på sekundære skærme."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Appen kan ikke åbnes på sekundære skærme."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Adskiller til opdelt skærm"</string>
diff --git a/libs/WindowManager/Shell/res/values-de/strings.xml b/libs/WindowManager/Shell/res/values-de/strings.xml
index 9a4b20eb4d3a..caca8b42154e 100644
--- a/libs/WindowManager/Shell/res/values-de/strings.xml
+++ b/libs/WindowManager/Shell/res/values-de/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Aus Stash entfernen"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Die App funktioniert unter Umständen im Modus für geteilten Bildschirm nicht."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Das Teilen des Bildschirms wird in dieser App nicht unterstützt."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Diese App kann nur in einem einzigen Fenster geöffnet werden."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Die App funktioniert auf einem sekundären Display möglicherweise nicht."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Die App unterstützt den Start auf sekundären Displays nicht."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Bildschirmteiler"</string>
diff --git a/libs/WindowManager/Shell/res/values-el/strings.xml b/libs/WindowManager/Shell/res/values-el/strings.xml
index 8aca81eb8129..ffb4fb0169a9 100644
--- a/libs/WindowManager/Shell/res/values-el/strings.xml
+++ b/libs/WindowManager/Shell/res/values-el/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Κατάργηση απόκρυψης"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Η εφαρμογή ενδέχεται να μην λειτουργεί με διαχωρισμό οθόνης."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Η εφαρμογή δεν υποστηρίζει διαχωρισμό οθόνης."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Αυτή η εφαρμογή μπορεί να ανοιχθεί μόνο σε 1 παράθυρο."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Η εφαρμογή ίσως να μην λειτουργήσει σε δευτερεύουσα οθόνη."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Η εφαρμογή δεν υποστηρίζει την εκκίνηση σε δευτερεύουσες οθόνες."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Διαχωριστικό οθόνης"</string>
diff --git a/libs/WindowManager/Shell/res/values-en-rAU/strings.xml b/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
index ec44597c7414..c71011d3b359 100644
--- a/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"App may not work with split-screen."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"App does not support split-screen."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"This app can only be opened in one window."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"App may not work on a secondary display."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"App does not support launch on secondary displays."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Split screen divider"</string>
diff --git a/libs/WindowManager/Shell/res/values-en-rCA/strings.xml b/libs/WindowManager/Shell/res/values-en-rCA/strings.xml
index 91875c5ffbe8..05091fb11864 100644
--- a/libs/WindowManager/Shell/res/values-en-rCA/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rCA/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"App may not work with split-screen."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"App does not support split-screen."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"This app can only be opened in 1 window."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"App may not work on a secondary display."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"App does not support launch on secondary displays."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Split-screen divider"</string>
diff --git a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
index ec44597c7414..c71011d3b359 100644
--- a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"App may not work with split-screen."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"App does not support split-screen."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"This app can only be opened in one window."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"App may not work on a secondary display."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"App does not support launch on secondary displays."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Split screen divider"</string>
diff --git a/libs/WindowManager/Shell/res/values-en-rIN/strings.xml b/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
index ec44597c7414..c71011d3b359 100644
--- a/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"App may not work with split-screen."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"App does not support split-screen."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"This app can only be opened in one window."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"App may not work on a secondary display."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"App does not support launch on secondary displays."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Split screen divider"</string>
diff --git a/libs/WindowManager/Shell/res/values-en-rXC/strings.xml b/libs/WindowManager/Shell/res/values-en-rXC/strings.xml
index ace138787570..2993fe7b81f4 100644
--- a/libs/WindowManager/Shell/res/values-en-rXC/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rXC/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‏‏‏‎‏‎‎‎‎‏‏‏‏‎‎‎‏‏‎‎‎‏‎‏‎‏‏‎‏‏‏‎‎‏‏‏‏‏‎‎‎‏‏‎‏‏‏‎‎‎‎‎‎‎‏‏‏‎‎Unstash‎‏‎‎‏‎"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‎‎‏‎‎‏‎‎‎‎‏‎‏‏‏‎‎‏‏‎‎‎‎‎‎‎‏‎‎‎‏‎‎‏‏‎‏‏‏‎‏‏‎‏‎‏‏‏‏‏‏‏‏‏‏‎‎‎App may not work with split-screen.‎‏‎‎‏‎"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‏‎‏‏‎‎‏‏‏‏‏‏‏‎‏‎‎‏‎‏‎‎‏‎‏‎‎‏‏‏‎‎‏‎‏‎‏‏‎‎‏‏‎‎‏‎‎‎‎‎‏‏‎‏‏‏‎‏‎App does not support split-screen.‎‏‎‎‏‎"</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‎‏‏‎‎‎‎‏‎‎‏‏‎‏‎‎‏‎‎‎‏‏‎‎‎‏‎‎‏‏‏‏‎‎‎‏‎‏‎‎‏‏‏‎‎‎‎‎‏‏‎‏‏‎‎‎‏‎This app can only be opened in 1 window.‎‏‎‎‏‎"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‎‏‎‎‎‏‎‎‏‎‏‎‏‏‏‎‏‎‏‎‏‎‎‏‎‏‎‏‏‏‏‎‏‏‏‎‏‏‎‏‏‎‎‎‏‎‎‏‎‎‏‎‎‏‏‏‏‎App may not work on a secondary display.‎‏‎‎‏‎"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‏‎‏‎‏‎‎‏‏‏‎‏‏‏‏‏‏‎‎‎‎‎‎‏‏‏‎‏‎‎‎‏‎‎‎‏‏‎‏‎‎‎‏‎‎‎‎‏‏‏‎‏‎‏‏‎‎‏‎App does not support launch on secondary displays.‎‏‎‎‏‎"</string>
<string name="accessibility_divider" msgid="703810061635792791">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‏‏‏‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‎‎‎‎‏‎‏‏‎‏‎‏‎‎‏‎‎‏‎‎‏‎‏‏‎‏‎‏‏‏‏‏‎‎‏‎‏‏‏‎Split-screen divider‎‏‎‎‏‎"</string>
diff --git a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
index 704e696e59db..0eaca8ba040f 100644
--- a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
+++ b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Dejar de almacenar de manera segura"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Es posible que la app no funcione en el modo de pantalla dividida."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"La app no es compatible con la función de pantalla dividida."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Esta app solo puede estar abierta en 1 ventana."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Es posible que la app no funcione en una pantalla secundaria."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"La app no puede iniciarse en pantallas secundarias."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Divisor de pantalla dividida"</string>
diff --git a/libs/WindowManager/Shell/res/values-es/strings.xml b/libs/WindowManager/Shell/res/values-es/strings.xml
index 2ad8b53732bc..9c8fed17859b 100644
--- a/libs/WindowManager/Shell/res/values-es/strings.xml
+++ b/libs/WindowManager/Shell/res/values-es/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"No esconder"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Es posible que la aplicación no funcione con la pantalla dividida."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"La aplicación no admite la pantalla dividida."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Esta aplicación solo puede abrirse en una ventana."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Es posible que la aplicación no funcione en una pantalla secundaria."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"La aplicación no se puede abrir en pantallas secundarias."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Dividir la pantalla"</string>
diff --git a/libs/WindowManager/Shell/res/values-et/strings.xml b/libs/WindowManager/Shell/res/values-et/strings.xml
index 359a06d49696..e8cbe5387410 100644
--- a/libs/WindowManager/Shell/res/values-et/strings.xml
+++ b/libs/WindowManager/Shell/res/values-et/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Eemalda hoidlast"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Rakendus ei pruugi poolitatud ekraaniga töötada."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Rakendus ei toeta jagatud ekraani."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Selle rakenduse saab avada ainult ühes aknas."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Rakendus ei pruugi teisesel ekraanil töötada."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Rakendus ei toeta teisestel ekraanidel käivitamist."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Ekraanijagaja"</string>
diff --git a/libs/WindowManager/Shell/res/values-eu/strings.xml b/libs/WindowManager/Shell/res/values-eu/strings.xml
index f3e9b8f3a384..4417668657e2 100644
--- a/libs/WindowManager/Shell/res/values-eu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-eu/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Ez gorde"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Baliteke aplikazioak ez funtzionatzea pantaila zatituan."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplikazioak ez du onartzen pantaila zatitua"</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Leiho bakar batean ireki daiteke aplikazioa."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Baliteke aplikazioak ez funtzionatzea bigarren mailako pantailetan."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikazioa ezin da abiarazi bigarren mailako pantailatan."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Pantaila-zatitzailea"</string>
diff --git a/libs/WindowManager/Shell/res/values-fa/strings.xml b/libs/WindowManager/Shell/res/values-fa/strings.xml
index 58f221e32ddb..7375faf8b30f 100644
--- a/libs/WindowManager/Shell/res/values-fa/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fa/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"لغو مخفی‌سازی"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"ممکن است برنامه با «صفحهٔ دونیمه» کار نکند."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"برنامه از تقسیم صفحه پشتیبانی نمی‌کند."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"این برنامه فقط در ۱ پنجره می‌تواند باز شود."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ممکن است برنامه در نمایشگر ثانویه کار نکند."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"برنامه از راه‌اندازی در نمایشگرهای ثانویه پشتیبانی نمی‌کند."</string>
<string name="accessibility_divider" msgid="703810061635792791">"تقسیم‌کننده صفحه"</string>
diff --git a/libs/WindowManager/Shell/res/values-fi/strings.xml b/libs/WindowManager/Shell/res/values-fi/strings.xml
index 191a21e1b14a..7729d1c62f30 100644
--- a/libs/WindowManager/Shell/res/values-fi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fi/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Poista turvasäilytyksestä"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Sovellus ei ehkä toimi jaetulla näytöllä."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Sovellus ei tue jaetun näytön tilaa."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Tämän sovelluksen voi avata vain yhdessä ikkunassa."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Sovellus ei ehkä toimi toissijaisella näytöllä."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Sovellus ei tue käynnistämistä toissijaisilla näytöillä."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Näytön jakaja"</string>
diff --git a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
index 587c2950d358..634880072d47 100644
--- a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Retirer de la réserve"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Il est possible que l\'application ne fonctionne pas en mode Écran partagé."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"L\'application n\'est pas compatible avec l\'écran partagé."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Cette application ne peut être ouverte que dans une fenêtre."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Il est possible que l\'application ne fonctionne pas sur un écran secondaire."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"L\'application ne peut pas être lancée sur des écrans secondaires."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Séparateur d\'écran partagé"</string>
diff --git a/libs/WindowManager/Shell/res/values-fr/strings.xml b/libs/WindowManager/Shell/res/values-fr/strings.xml
index 0ede879cb7f7..184221345b23 100644
--- a/libs/WindowManager/Shell/res/values-fr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fr/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Il est possible que l\'application ne fonctionne pas en mode Écran partagé."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Application incompatible avec l\'écran partagé."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Cette appli ne peut être ouverte que dans 1 fenêtre."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Il est possible que l\'application ne fonctionne pas sur un écran secondaire."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"L\'application ne peut pas être lancée sur des écrans secondaires."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Séparateur d\'écran partagé"</string>
diff --git a/libs/WindowManager/Shell/res/values-gl/strings.xml b/libs/WindowManager/Shell/res/values-gl/strings.xml
index 1c692140e57a..2e05d4c3b548 100644
--- a/libs/WindowManager/Shell/res/values-gl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-gl/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Non esconder"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Pode que a aplicación non funcione coa pantalla dividida."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"A aplicación non é compatible coa función de pantalla dividida."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Esta aplicación só se pode abrir en 1 ventá."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"É posible que a aplicación non funcione nunha pantalla secundaria."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"A aplicación non se pode iniciar en pantallas secundarias."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Divisor de pantalla dividida"</string>
diff --git a/libs/WindowManager/Shell/res/values-gu/strings.xml b/libs/WindowManager/Shell/res/values-gu/strings.xml
index c50445c13778..e680298c90bc 100644
--- a/libs/WindowManager/Shell/res/values-gu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-gu/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"બતાવો"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"વિભાજિત-સ્ક્રીન સાથે ઍપ કદાચ કામ ન કરે."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"ઍપ્લિકેશન સ્ક્રીન-વિભાજનનું સમર્થન કરતી નથી."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"આ ઍપ માત્ર 1 વિન્ડોમાં ખોલી શકાય છે."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ઍપ્લિકેશન ગૌણ ડિસ્પ્લે પર કદાચ કામ ન કરે."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ઍપ્લિકેશન ગૌણ ડિસ્પ્લે પર લૉન્ચનું સમર્થન કરતી નથી."</string>
<string name="accessibility_divider" msgid="703810061635792791">"સ્પ્લિટ-સ્ક્રીન વિભાજક"</string>
diff --git a/libs/WindowManager/Shell/res/values-hr/strings.xml b/libs/WindowManager/Shell/res/values-hr/strings.xml
index bf54411efdde..23a5970d805e 100644
--- a/libs/WindowManager/Shell/res/values-hr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hr/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Poništite sakrivanje"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Aplikacija možda neće funkcionirati s podijeljenim zaslonom."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplikacija ne podržava podijeljeni zaslon."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Ova se aplikacija može otvoriti samo u jednom prozoru."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikacija možda neće funkcionirati na sekundarnom zaslonu."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikacija ne podržava pokretanje na sekundarnim zaslonima."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Razdjelnik podijeljenog zaslona"</string>
diff --git a/libs/WindowManager/Shell/res/values-hu/strings.xml b/libs/WindowManager/Shell/res/values-hu/strings.xml
index 01f533f61a7f..1bbbdb7786b4 100644
--- a/libs/WindowManager/Shell/res/values-hu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hu/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Félretevés megszüntetése"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Lehet, hogy az alkalmazás nem működik osztott képernyős nézetben."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Az alkalmazás nem támogatja az osztott képernyős nézetet."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Ez az alkalmazás csak egy ablakban nyitható meg."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Előfordulhat, hogy az alkalmazás nem működik másodlagos kijelzőn."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Az alkalmazást nem lehet másodlagos kijelzőn elindítani."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Elválasztó az osztott nézetben"</string>
diff --git a/libs/WindowManager/Shell/res/values-hy/strings.xml b/libs/WindowManager/Shell/res/values-hy/strings.xml
index f8ead655cc05..6eef4afda3ff 100644
--- a/libs/WindowManager/Shell/res/values-hy/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hy/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Ցուցադրել"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Հավելվածը չի կարող աշխատել տրոհված էկրանի ռեժիմում։"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Հավելվածը չի աջակցում էկրանի տրոհումը:"</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Այս հավելվածը հնարավոր է բացել միայն մեկ պատուհանում։"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Հավելվածը կարող է չաշխատել լրացուցիչ էկրանի վրա"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Հավելվածը չի աջակցում գործարկումը լրացուցիչ էկրանների վրա"</string>
<string name="accessibility_divider" msgid="703810061635792791">"Տրոհված էկրանի բաժանիչ"</string>
diff --git a/libs/WindowManager/Shell/res/values-in/strings.xml b/libs/WindowManager/Shell/res/values-in/strings.xml
index dce6b38f8e5c..61a9558972e0 100644
--- a/libs/WindowManager/Shell/res/values-in/strings.xml
+++ b/libs/WindowManager/Shell/res/values-in/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Batalkan stash"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Aplikasi mungkin tidak berfungsi dengan layar terpisah."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"App tidak mendukung layar terpisah."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Aplikasi ini hanya dapat dibuka di 1 jendela."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikasi mungkin tidak berfungsi pada layar sekunder."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikasi tidak mendukung peluncuran pada layar sekunder."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Pembagi layar terpisah"</string>
diff --git a/libs/WindowManager/Shell/res/values-is/strings.xml b/libs/WindowManager/Shell/res/values-is/strings.xml
index f4c2221b094f..0b873bc82e63 100644
--- a/libs/WindowManager/Shell/res/values-is/strings.xml
+++ b/libs/WindowManager/Shell/res/values-is/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Taka úr geymslu"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Hugsanlega virkar forritið ekki með skjáskiptingu."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Forritið styður ekki að skjánum sé skipt."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Aðeins er hægt að opna þetta forrit í 1 glugga."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Hugsanlegt er að forritið virki ekki á öðrum skjá."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Forrit styður ekki opnun á öðrum skjá."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Skjáskipting"</string>
diff --git a/libs/WindowManager/Shell/res/values-it/strings.xml b/libs/WindowManager/Shell/res/values-it/strings.xml
index af5a0fb306d9..da4d0bbe1951 100644
--- a/libs/WindowManager/Shell/res/values-it/strings.xml
+++ b/libs/WindowManager/Shell/res/values-it/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Annulla accantonamento"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"L\'app potrebbe non funzionare con lo schermo diviso."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"L\'app non supporta la modalità Schermo diviso."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Questa app può essere aperta soltanto in 1 finestra."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"L\'app potrebbe non funzionare su un display secondario."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"L\'app non supporta l\'avvio su display secondari."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Strumento per schermo diviso"</string>
diff --git a/libs/WindowManager/Shell/res/values-iw/strings.xml b/libs/WindowManager/Shell/res/values-iw/strings.xml
index 7a07e6c8e34c..e9a53ddcd1e6 100644
--- a/libs/WindowManager/Shell/res/values-iw/strings.xml
+++ b/libs/WindowManager/Shell/res/values-iw/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"ביטול ההסתרה הזמנית"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"ייתכן שהאפליקציה לא תפעל במסך מפוצל."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"האפליקציה אינה תומכת במסך מפוצל."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"ניתן לפתוח את האפליקציה הזו רק בחלון אחד."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ייתכן שהאפליקציה לא תפעל במסך משני."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"האפליקציה אינה תומכת בהפעלה במסכים משניים."</string>
<string name="accessibility_divider" msgid="703810061635792791">"מחלק מסך מפוצל"</string>
diff --git a/libs/WindowManager/Shell/res/values-ja/strings.xml b/libs/WindowManager/Shell/res/values-ja/strings.xml
index f3da0954dc07..2930cc3747b9 100644
--- a/libs/WindowManager/Shell/res/values-ja/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ja/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"表示"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"アプリは分割画面では動作しないことがあります。"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"アプリで分割画面がサポートされていません。"</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"このアプリはウィンドウが 1 つの場合のみ開くことができます。"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"アプリはセカンダリ ディスプレイでは動作しないことがあります。"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"アプリはセカンダリ ディスプレイでの起動に対応していません。"</string>
<string name="accessibility_divider" msgid="703810061635792791">"分割画面の分割線"</string>
diff --git a/libs/WindowManager/Shell/res/values-ka/strings.xml b/libs/WindowManager/Shell/res/values-ka/strings.xml
index 36d4be07d82f..848be3f86392 100644
--- a/libs/WindowManager/Shell/res/values-ka/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ka/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"გადანახვის გაუქმება"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"აპმა შეიძლება არ იმუშაოს გაყოფილი ეკრანის რეჟიმში."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"ეკრანის გაყოფა არ არის მხარდაჭერილი აპის მიერ."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"ამ აპის გახსნა შესაძლებელია მხოლოდ 1 ფანჯარაში."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"აპმა შეიძლება არ იმუშაოს მეორეულ ეკრანზე."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"აპს არ გააჩნია მეორეული ეკრანის მხარდაჭერა."</string>
<string name="accessibility_divider" msgid="703810061635792791">"გაყოფილი ეკრანის რეჟიმის გამყოფი"</string>
diff --git a/libs/WindowManager/Shell/res/values-kk/strings.xml b/libs/WindowManager/Shell/res/values-kk/strings.xml
index e7bcc996cfdf..8d08ccabb623 100644
--- a/libs/WindowManager/Shell/res/values-kk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-kk/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Көрсету"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Қолданба экранды бөлу режимінде жұмыс істемеуі мүмкін."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Қодланба бөлінген экранды қолдамайды."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Бұл қолданбаны тек 1 терезеден ашуға болады."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Қолданба қосымша дисплейде жұмыс істемеуі мүмкін."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Қолданба қосымша дисплейлерде іске қосуды қолдамайды."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Бөлінген экран бөлгіші"</string>
diff --git a/libs/WindowManager/Shell/res/values-km/strings.xml b/libs/WindowManager/Shell/res/values-km/strings.xml
index 04142d7ec5c2..7c4ea57e81d8 100644
--- a/libs/WindowManager/Shell/res/values-km/strings.xml
+++ b/libs/WindowManager/Shell/res/values-km/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"ឈប់លាក់ជាបណ្ដោះអាសន្ន"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"កម្មវិធី​អាចនឹងមិន​ដំណើរការ​ជាមួយ​មុខងារបំបែកអេក្រង់​ទេ។"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"កម្មវិធីមិនគាំទ្រអេក្រង់បំបែកជាពីរទេ"</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"កម្មវិធីនេះអាចបើកនៅក្នុងវិនដូតែ 1 ប៉ុណ្ណោះ។"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"កម្មវិធីនេះ​ប្រហែល​ជាមិនដំណើរការ​នៅលើ​អេក្រង់បន្ទាប់បន្សំទេ។"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"កម្មវិធី​នេះមិន​អាច​ចាប់ផ្តើម​នៅលើ​អេក្រង់បន្ទាប់បន្សំបានទេ។"</string>
<string name="accessibility_divider" msgid="703810061635792791">"កម្មវិធីចែកអេក្រង់បំបែក"</string>
diff --git a/libs/WindowManager/Shell/res/values-kn/strings.xml b/libs/WindowManager/Shell/res/values-kn/strings.xml
index e2c86a9e7be6..72906174a65c 100644
--- a/libs/WindowManager/Shell/res/values-kn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-kn/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"ಅನ್‌ಸ್ಟ್ಯಾಶ್ ಮಾಡಿ"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"ವಿಭಜಿಸಿದ ಸ್ಕ್ರೀನ್‌ನಲ್ಲಿ ಆ್ಯಪ್ ಕೆಲಸ ಮಾಡದೇ ಇರಬಹುದು."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"ಅಪ್ಲಿಕೇಶನ್ ಸ್ಪ್ಲಿಟ್ ಸ್ಕ್ರೀನ್ ಅನ್ನು ಬೆಂಬಲಿಸುವುದಿಲ್ಲ."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"ಈ ಆ್ಯಪ್ ಅನ್ನು 1 ವಿಂಡೋದಲ್ಲಿ ಮಾತ್ರ ತೆರೆಯಬಹುದು."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ಸೆಕೆಂಡರಿ ಡಿಸ್‌ಪ್ಲೇಗಳಲ್ಲಿ ಅಪ್ಲಿಕೇಶನ್‌ ಕಾರ್ಯ ನಿರ್ವಹಿಸದೇ ಇರಬಹುದು."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ಸೆಕೆಂಡರಿ ಡಿಸ್‌ಪ್ಲೇಗಳಲ್ಲಿ ಪ್ರಾರಂಭಿಸುವಿಕೆಯನ್ನು ಅಪ್ಲಿಕೇಶನ್ ಬೆಂಬಲಿಸುವುದಿಲ್ಲ."</string>
<string name="accessibility_divider" msgid="703810061635792791">"ಸ್ಪ್ಲಿಟ್-ಪರದೆ ಡಿವೈಡರ್"</string>
diff --git a/libs/WindowManager/Shell/res/values-ko/strings.xml b/libs/WindowManager/Shell/res/values-ko/strings.xml
index baa245aaa180..59b405ff1e72 100644
--- a/libs/WindowManager/Shell/res/values-ko/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ko/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"숨기기 취소"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"앱이 분할 화면에서 작동하지 않을 수 있습니다."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"앱이 화면 분할을 지원하지 않습니다."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"이 앱은 창 1개에서만 열 수 있습니다."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"앱이 보조 디스플레이에서 작동하지 않을 수도 있습니다."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"앱이 보조 디스플레이에서의 실행을 지원하지 않습니다."</string>
<string name="accessibility_divider" msgid="703810061635792791">"화면 분할기"</string>
diff --git a/libs/WindowManager/Shell/res/values-ky/strings.xml b/libs/WindowManager/Shell/res/values-ky/strings.xml
index fdf33911e8f6..69ec8ebc41f4 100644
--- a/libs/WindowManager/Shell/res/values-ky/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ky/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Сейфтен чыгаруу"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Колдонмодо экран бөлүнбөшү мүмкүн."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Колдонмодо экран бөлүнбөйт."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Бул колдонмону 1 терезеде гана ачууга болот."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Колдонмо кошумча экранда иштебей коюшу мүмкүн."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Колдонмону кошумча экрандарда иштетүүгө болбойт."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Экранды бөлгүч"</string>
diff --git a/libs/WindowManager/Shell/res/values-lo/strings.xml b/libs/WindowManager/Shell/res/values-lo/strings.xml
index 30631f963cd3..d5ea3cf37c8b 100644
--- a/libs/WindowManager/Shell/res/values-lo/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lo/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"ເອົາອອກຈາກບ່ອນເກັບສ່ວນຕົວ"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"ແອັບອາດໃຊ້ບໍ່ໄດ້ກັບການແບ່ງໜ້າຈໍ."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"ແອັບບໍ່ຮອງຮັບໜ້າຈໍແບບແຍກກັນ."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"ແອັບນີ້ສາມາດເປີດໄດ້ໃນ 1 ໜ້າຈໍເທົ່ານັ້ນ."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ແອັບອາດບໍ່ສາມາດໃຊ້ໄດ້ໃນໜ້າຈໍທີສອງ."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ແອັບບໍ່ຮອງຮັບການເປີດໃນໜ້າຈໍທີສອງ."</string>
<string name="accessibility_divider" msgid="703810061635792791">"ຕົວຂັ້ນການແບ່ງໜ້າຈໍ"</string>
diff --git a/libs/WindowManager/Shell/res/values-lt/strings.xml b/libs/WindowManager/Shell/res/values-lt/strings.xml
index bac36816893d..922f5b59a703 100644
--- a/libs/WindowManager/Shell/res/values-lt/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lt/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Nebeslėpti"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Programa gali neveikti naudojant išskaidyto ekrano režimą."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Programoje nepalaikomas skaidytas ekranas."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Šią programą galima atidaryti tik viename lange."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Programa gali neveikti antriniame ekrane."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Programa nepalaiko paleisties antriniuose ekranuose."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Skaidyto ekrano daliklis"</string>
diff --git a/libs/WindowManager/Shell/res/values-lv/strings.xml b/libs/WindowManager/Shell/res/values-lv/strings.xml
index c74d4f949485..08ac9280bbab 100644
--- a/libs/WindowManager/Shell/res/values-lv/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lv/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Rādīt"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Iespējams, lietotne nedarbosies ekrāna sadalīšanas režīmā."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Lietotnē netiek atbalstīta ekrāna sadalīšana."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Šo lietotni var atvērt tikai vienā logā."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Lietotne, iespējams, nedarbosies sekundārajā displejā."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Lietotnē netiek atbalstīta palaišana sekundārajos displejos."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Ekrāna sadalītājs"</string>
diff --git a/libs/WindowManager/Shell/res/values-mk/strings.xml b/libs/WindowManager/Shell/res/values-mk/strings.xml
index d64097fb19e1..ae71ae90043f 100644
--- a/libs/WindowManager/Shell/res/values-mk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mk/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Прикажете"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Апликацијата може да не работи со поделен екран."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Апликацијата не поддржува поделен екран."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Апликацијава може да се отвори само во еден прозорец."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Апликацијата може да не функционира на друг екран."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Апликацијата не поддржува стартување на други екрани."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Разделник на поделен екран"</string>
diff --git a/libs/WindowManager/Shell/res/values-mn/strings.xml b/libs/WindowManager/Shell/res/values-mn/strings.xml
index b9cf9458722a..c1950a1d3241 100644
--- a/libs/WindowManager/Shell/res/values-mn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mn/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Ил гаргах"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Апп хуваагдсан дэлгэц дээр ажиллахгүй байж болзошгүй."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Энэ апп нь дэлгэц хуваах тохиргоог дэмждэггүй."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Энэ аппыг зөвхөн 1 цонхонд нээх боломжтой."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Апп хоёрдогч дэлгэцэд ажиллахгүй."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Аппыг хоёрдогч дэлгэцэд эхлүүлэх боломжгүй."</string>
<string name="accessibility_divider" msgid="703810061635792791">"\"Дэлгэц хуваах\" хуваагч"</string>
diff --git a/libs/WindowManager/Shell/res/values-mr/strings.xml b/libs/WindowManager/Shell/res/values-mr/strings.xml
index 19de9764a2f5..29821f6a8bbc 100644
--- a/libs/WindowManager/Shell/res/values-mr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mr/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"अनस्टॅश करा"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"अ‍ॅप कदाचित स्प्लिट स्क्रीनसह काम करू शकत नाही."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"अ‍ॅप स्क्रीन-विभाजनास समर्थन देत नाही."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"हे अ‍ॅप फक्त एका विंडोमध्ये उघडले जाऊ शकते."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"दुसऱ्या डिस्प्लेवर अ‍ॅप कदाचित चालणार नाही."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"दुसऱ्या डिस्प्लेवर अ‍ॅप लाँच होणार नाही."</string>
<string name="accessibility_divider" msgid="703810061635792791">"विभाजित-स्क्रीन विभाजक"</string>
diff --git a/libs/WindowManager/Shell/res/values-ms/strings.xml b/libs/WindowManager/Shell/res/values-ms/strings.xml
index 4581a7740238..c3db19d46a55 100644
--- a/libs/WindowManager/Shell/res/values-ms/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ms/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Tunjukkan"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Apl mungkin tidak berfungsi dengan skrin pisah."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Apl tidak menyokong skrin pisah."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Apl ini hanya boleh dibuka dalam 1 tetingkap."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Apl mungkin tidak berfungsi pada paparan kedua."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Apl tidak menyokong pelancaran pada paparan kedua."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Pembahagi skrin pisah"</string>
diff --git a/libs/WindowManager/Shell/res/values-my/strings.xml b/libs/WindowManager/Shell/res/values-my/strings.xml
index 6f8fed97c1aa..b2bb37dd7730 100644
--- a/libs/WindowManager/Shell/res/values-my/strings.xml
+++ b/libs/WindowManager/Shell/res/values-my/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"မသိုဝှက်ရန်"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"မျက်နှာပြင် ခွဲ၍ပြသခြင်းဖြင့် အက်ပ်သည် အလုပ်မလုပ်ပါ။"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"အက်ပ်သည် မျက်နှာပြင်ခွဲပြရန် ပံ့ပိုးထားခြင်းမရှိပါ။"</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"ဤအက်ပ်ကို ဝင်းဒိုး ၁ ခုတွင်သာ ဖွင့်နိုင်သည်။"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ဤအက်ပ်အနေဖြင့် ဒုတိယဖန်သားပြင်ပေါ်တွင် အလုပ်လုပ်မည် မဟုတ်ပါ။"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ဤအက်ပ်အနေဖြင့် ဖွင့်ရန်စနစ်ကို ဒုတိယဖန်သားပြင်မှ အသုံးပြုရန် ပံ့ပိုးမထားပါ။"</string>
<string name="accessibility_divider" msgid="703810061635792791">"မျက်နှာပြင်ခွဲခြမ်း ပိုင်းခြားပေးသည့်စနစ်"</string>
diff --git a/libs/WindowManager/Shell/res/values-nb/strings.xml b/libs/WindowManager/Shell/res/values-nb/strings.xml
index e0108e3dec2b..90b9dfca6116 100644
--- a/libs/WindowManager/Shell/res/values-nb/strings.xml
+++ b/libs/WindowManager/Shell/res/values-nb/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Avslutt oppbevaring"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Det kan hende at appen ikke fungerer med delt skjerm."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Appen støtter ikke delt skjerm."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Denne appen kan bare åpnes i ett vindu."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Appen fungerer kanskje ikke på en sekundær skjerm."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Appen kan ikke kjøres på sekundære skjermer."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Skilleelement for delt skjerm"</string>
diff --git a/libs/WindowManager/Shell/res/values-nl/strings.xml b/libs/WindowManager/Shell/res/values-nl/strings.xml
index f6a69757055f..f9f4ef4a4b63 100644
--- a/libs/WindowManager/Shell/res/values-nl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-nl/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Niet meer verbergen"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"De app werkt mogelijk niet met gesplitst scherm."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"App biedt geen ondersteuning voor gesplitst scherm."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Deze app kan slechts in 1 venster worden geopend."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"App werkt mogelijk niet op een secundair scherm."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"App kan niet op secundaire displays worden gestart."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Scheiding voor gesplitst scherm"</string>
diff --git a/libs/WindowManager/Shell/res/values-or/strings.xml b/libs/WindowManager/Shell/res/values-or/strings.xml
index e3e8b84a2e46..5a76a6f3f22a 100644
--- a/libs/WindowManager/Shell/res/values-or/strings.xml
+++ b/libs/WindowManager/Shell/res/values-or/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"ଦେଖାନ୍ତୁ"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"ସ୍ପ୍ଲିଟ୍-ସ୍କ୍ରିନରେ ଆପ୍ କାମ କରିନପାରେ।"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"ଆପ୍‍ ସ୍ପ୍ଲିଟ୍‍-ସ୍କ୍ରୀନକୁ ସପୋର୍ଟ କରେ ନାହିଁ।"</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"ଏହି ଆପକୁ କେବଳ 1ଟି ୱିଣ୍ଡୋରେ ଖୋଲାଯାଇପାରିବ।"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ସେକେଣ୍ଡାରୀ ଡିସପ୍ଲେରେ ଆପ୍‍ କାମ ନକରିପାରେ।"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ସେକେଣ୍ଡାରୀ ଡିସପ୍ଲେରେ ଆପ୍‍ ଲଞ୍ଚ ସପୋର୍ଟ କରେ ନାହିଁ।"</string>
<string name="accessibility_divider" msgid="703810061635792791">"ସ୍ପ୍ଲିଟ୍‍-ସ୍କ୍ରୀନ ବିଭାଜକ"</string>
diff --git a/libs/WindowManager/Shell/res/values-pa/strings.xml b/libs/WindowManager/Shell/res/values-pa/strings.xml
index 16310ffe0f6c..617c95eec8d9 100644
--- a/libs/WindowManager/Shell/res/values-pa/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pa/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"ਅਣਸਟੈਸ਼"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"ਹੋ ਸਕਦਾ ਹੈ ਕਿ ਐਪ ਸਪਲਿਟ-ਸਕ੍ਰੀਨ ਨਾਲ ਕੰਮ ਨਾ ਕਰੇ।"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"ਐਪ ਸਪਲਿਟ-ਸਕ੍ਰੀਨ ਨੂੰ ਸਮਰਥਨ ਨਹੀਂ ਕਰਦੀ।"</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"ਇਹ ਐਪ ਸਿਰਫ਼ 1 ਵਿੰਡੋ ਵਿੱਚ ਖੋਲ੍ਹੀ ਜਾ ਸਕਦੀ ਹੈ।"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ਹੋ ਸਕਦਾ ਹੈ ਕਿ ਐਪ ਸੈਕੰਡਰੀ ਡਿਸਪਲੇ \'ਤੇ ਕੰਮ ਨਾ ਕਰੇ।"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ਐਪ ਸੈਕੰਡਰੀ ਡਿਸਪਲੇਆਂ \'ਤੇ ਲਾਂਚ ਕਰਨ ਦਾ ਸਮਰਥਨ ਨਹੀਂ ਕਰਦੀ"</string>
<string name="accessibility_divider" msgid="703810061635792791">"ਸਪਲਿਟ-ਸਕ੍ਰੀਨ ਡਿਵਾਈਡਰ"</string>
diff --git a/libs/WindowManager/Shell/res/values-pl/strings.xml b/libs/WindowManager/Shell/res/values-pl/strings.xml
index fd6434af15df..4a17ec74bada 100644
--- a/libs/WindowManager/Shell/res/values-pl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pl/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Zabierz ze schowka"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Aplikacja może nie działać przy podzielonym ekranie."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplikacja nie obsługuje dzielonego ekranu."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Ta aplikacja może być otwarta tylko w 1 oknie."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikacja może nie działać na dodatkowym ekranie."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikacja nie obsługuje uruchamiania na dodatkowych ekranach."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Linia dzielenia ekranu"</string>
diff --git a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
index 8cf331419b86..69be68ead461 100644
--- a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Exibir"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"É possível que o app não funcione com a tela dividida."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"O app não é compatível com a divisão de tela."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Este app só pode ser aberto em uma única janela."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"É possível que o app não funcione em uma tela secundária."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"O app não é compatível com a inicialização em telas secundárias."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Divisor de tela"</string>
diff --git a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
index d4d5ae62499e..13e83ac0bcf7 100644
--- a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Remover do armazenamento"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"A app pode não funcionar com o ecrã dividido."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"A app não é compatível com o ecrã dividido."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Esta app só pode ser aberta em 1 janela."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"A app pode não funcionar num ecrã secundário."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"A app não é compatível com o início em ecrãs secundários."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Divisor do ecrã dividido"</string>
diff --git a/libs/WindowManager/Shell/res/values-pt/strings.xml b/libs/WindowManager/Shell/res/values-pt/strings.xml
index 8cf331419b86..69be68ead461 100644
--- a/libs/WindowManager/Shell/res/values-pt/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Exibir"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"É possível que o app não funcione com a tela dividida."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"O app não é compatível com a divisão de tela."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Este app só pode ser aberto em uma única janela."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"É possível que o app não funcione em uma tela secundária."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"O app não é compatível com a inicialização em telas secundárias."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Divisor de tela"</string>
diff --git a/libs/WindowManager/Shell/res/values-ro/strings.xml b/libs/WindowManager/Shell/res/values-ro/strings.xml
index 44220da9494c..c112a9d26a7c 100644
--- a/libs/WindowManager/Shell/res/values-ro/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ro/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Anulează stocarea"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Este posibil ca aplicația să nu funcționeze cu ecranul împărțit."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplicația nu acceptă ecranul împărțit."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Aplicația poate fi deschisă într-o singură fereastră."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Este posibil ca aplicația să nu funcționeze pe un ecran secundar."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplicația nu acceptă lansare pe ecrane secundare."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Separator pentru ecranul împărțit"</string>
diff --git a/libs/WindowManager/Shell/res/values-ru/strings.xml b/libs/WindowManager/Shell/res/values-ru/strings.xml
index 88168954976b..489adc06e93d 100644
--- a/libs/WindowManager/Shell/res/values-ru/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ru/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Показать"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"В режиме разделения экрана приложение может работать нестабильно."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Приложение не поддерживает разделение экрана."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Это приложение можно открыть только в одном окне."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Приложение может не работать на дополнительном экране"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Приложение не поддерживает запуск на дополнительных экранах"</string>
<string name="accessibility_divider" msgid="703810061635792791">"Разделитель экрана"</string>
diff --git a/libs/WindowManager/Shell/res/values-si/strings.xml b/libs/WindowManager/Shell/res/values-si/strings.xml
index 37c1205941dd..32371148fce2 100644
--- a/libs/WindowManager/Shell/res/values-si/strings.xml
+++ b/libs/WindowManager/Shell/res/values-si/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"සඟවා තැබීම ඉවත් කරන්න"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"යෙදුම බෙදුම් තිරය සමග ක්‍රියා නොකළ හැකිය"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"යෙදුම බෙදුණු-තිරය සඳහා සහාය නොදක්වයි."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"මෙම යෙදුම විවෘත කළ හැක්කේ 1 කවුළුවක පමණයි."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"යෙදුම ද්විතියික සංදර්ශකයක ක්‍රියා නොකළ හැකිය."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"යෙදුම ද්විතීයික සංදර්ශක මත දියත් කිරීම සඳහා සහාය නොදක්වයි."</string>
<string name="accessibility_divider" msgid="703810061635792791">"බෙදුම්-තිර වෙන්කරණය"</string>
diff --git a/libs/WindowManager/Shell/res/values-sk/strings.xml b/libs/WindowManager/Shell/res/values-sk/strings.xml
index d6900d00d453..a7530215d010 100644
--- a/libs/WindowManager/Shell/res/values-sk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sk/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Zrušiť skrytie"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Aplikácia nemusí fungovať s rozdelenou obrazovkou."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplikácia nepodporuje rozdelenú obrazovku."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Táto aplikácia môže byť otvorená iba v jednom okne."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikácia nemusí fungovať na sekundárnej obrazovke."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikácia nepodporuje spúšťanie na sekundárnych obrazovkách."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Rozdeľovač obrazovky"</string>
diff --git a/libs/WindowManager/Shell/res/values-sl/strings.xml b/libs/WindowManager/Shell/res/values-sl/strings.xml
index b0a8587dca5a..b5d87333d85e 100644
--- a/libs/WindowManager/Shell/res/values-sl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sl/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Razkrij"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Aplikacija morda ne deluje v načinu razdeljenega zaslona."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplikacija ne podpira načina razdeljenega zaslona."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"To aplikacijo je mogoče odpreti samo v enem oknu."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikacija morda ne bo delovala na sekundarnem zaslonu."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikacija ne podpira zagona na sekundarnih zaslonih."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Razdelilnik zaslonov"</string>
diff --git a/libs/WindowManager/Shell/res/values-sq/strings.xml b/libs/WindowManager/Shell/res/values-sq/strings.xml
index f4a22d427b77..ebd644c7425f 100644
--- a/libs/WindowManager/Shell/res/values-sq/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sq/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Mos e fshih"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Aplikacioni mund të mos funksionojë me ekranin e ndarë."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplikacioni nuk mbështet ekranin e ndarë."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Ky aplikacion mund të hapet vetëm në 1 dritare."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikacioni mund të mos funksionojë në një ekran dytësor."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikacioni nuk mbështet nisjen në ekrane dytësore."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Ndarësi i ekranit të ndarë"</string>
diff --git a/libs/WindowManager/Shell/res/values-sr/strings.xml b/libs/WindowManager/Shell/res/values-sr/strings.xml
index 3d96c655c22e..d051ca350682 100644
--- a/libs/WindowManager/Shell/res/values-sr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sr/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Уклоните из тајне меморије"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Апликација можда неће радити са подељеним екраном."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Апликација не подржава подељени екран."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Ова апликација може да се отвори само у једном прозору."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Апликација можда неће функционисати на секундарном екрану."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Апликација не подржава покретање на секундарним екранима."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Разделник подељеног екрана"</string>
diff --git a/libs/WindowManager/Shell/res/values-sv/strings.xml b/libs/WindowManager/Shell/res/values-sv/strings.xml
index b7d2584b2564..cd46039b26b5 100644
--- a/libs/WindowManager/Shell/res/values-sv/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sv/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Återställ stash"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Appen kanske inte fungerar med delad skärm."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Appen har inte stöd för delad skärm."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Denna app kan bara vara öppen i ett fönster."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Appen kanske inte fungerar på en sekundär skärm."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Appen kan inte köras på en sekundär skärm."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Avdelare för delad skärm"</string>
diff --git a/libs/WindowManager/Shell/res/values-sw/strings.xml b/libs/WindowManager/Shell/res/values-sw/strings.xml
index fa46229c8894..345fbf81cd48 100644
--- a/libs/WindowManager/Shell/res/values-sw/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sw/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Fichua"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Huenda programu isifanye kazi kwenye skrini inayogawanywa."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Programu haiwezi kutumia skrini iliyogawanywa."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Programu hii inaweza kufunguliwa katika dirisha 1 pekee."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Huenda programu isifanye kazi kwenye dirisha lingine."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Programu hii haiwezi kufunguliwa kwenye madirisha mengine."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Kitenganishi cha skrini inayogawanywa"</string>
diff --git a/libs/WindowManager/Shell/res/values-te/strings.xml b/libs/WindowManager/Shell/res/values-te/strings.xml
index f0daac946fcf..0c0114afeed2 100644
--- a/libs/WindowManager/Shell/res/values-te/strings.xml
+++ b/libs/WindowManager/Shell/res/values-te/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"ఆన్‌స్టాచ్"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"స్క్రీన్ విభజనతో యాప్‌ పని చేయకపోవచ్చు."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"యాప్‌లో స్క్రీన్ విభజనకు మద్దతు లేదు."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"ఈ యాప్‌ను 1 విండోలో మాత్రమే తెరవవచ్చు."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ప్రత్యామ్నాయ డిస్‌ప్లేలో యాప్ పని చేయకపోవచ్చు."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ప్రత్యామ్నాయ డిస్‌ప్లేల్లో ప్రారంభానికి యాప్ మద్దతు లేదు."</string>
<string name="accessibility_divider" msgid="703810061635792791">"విభజన స్క్రీన్ విభాగిని"</string>
diff --git a/libs/WindowManager/Shell/res/values-th/strings.xml b/libs/WindowManager/Shell/res/values-th/strings.xml
index 74a55c346059..9f3a14610225 100644
--- a/libs/WindowManager/Shell/res/values-th/strings.xml
+++ b/libs/WindowManager/Shell/res/values-th/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"เอาออกจากที่เก็บส่วนตัว"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"แอปอาจใช้ไม่ได้กับโหมดแบ่งหน้าจอ"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"แอปไม่สนับสนุนการแยกหน้าจอ"</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"แอปนี้เปิดได้ใน 1 หน้าต่างเท่านั้น"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"แอปอาจไม่ทำงานในจอแสดงผลรอง"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"แอปไม่รองรับการเรียกใช้ในจอแสดงผลรอง"</string>
<string name="accessibility_divider" msgid="703810061635792791">"เส้นแบ่งหน้าจอ"</string>
diff --git a/libs/WindowManager/Shell/res/values-tl/strings.xml b/libs/WindowManager/Shell/res/values-tl/strings.xml
index 58ef31bf918c..c20a07f1cf90 100644
--- a/libs/WindowManager/Shell/res/values-tl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-tl/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"I-unstash"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Posibleng hindi gumana ang app sa split screen."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Hindi sinusuportahan ng app ang split-screen."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Sa 1 window lang puwedeng buksan ang app na ito."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Maaaring hindi gumana ang app sa pangalawang display."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Hindi sinusuportahan ng app ang paglulunsad sa mga pangalawang display."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Divider ng split-screen"</string>
diff --git a/libs/WindowManager/Shell/res/values-tr/strings.xml b/libs/WindowManager/Shell/res/values-tr/strings.xml
index 6ca95986658e..aeb86da2a6ed 100644
--- a/libs/WindowManager/Shell/res/values-tr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-tr/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Depolama"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Uygulama bölünmüş ekranda çalışmayabilir."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Uygulama bölünmüş ekranı desteklemiyor."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Bu uygulama yalnızca 1 pencerede açılabilir."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Uygulama ikincil ekranda çalışmayabilir."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Uygulama ikincil ekranlarda başlatılmayı desteklemiyor."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Bölünmüş ekran ayırıcı"</string>
diff --git a/libs/WindowManager/Shell/res/values-uk/strings.xml b/libs/WindowManager/Shell/res/values-uk/strings.xml
index 084fdb28c6b0..b589ed8c7739 100644
--- a/libs/WindowManager/Shell/res/values-uk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-uk/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Показати"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Додаток може не працювати в режимі розділеного екрана."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Додаток не підтримує розділення екрана."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Цей додаток можна відкрити лише в одному вікні."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Додаток може не працювати на додатковому екрані."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Додаток не підтримує запуск на додаткових екранах."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Розділювач екрана"</string>
diff --git a/libs/WindowManager/Shell/res/values-ur/strings.xml b/libs/WindowManager/Shell/res/values-ur/strings.xml
index 645be3731bb1..81672bff7545 100644
--- a/libs/WindowManager/Shell/res/values-ur/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ur/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"ممکن ہے کہ ایپ اسپلٹ اسکرین کے ساتھ کام نہ کرے۔"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"ایپ سپلٹ اسکرین کو سپورٹ نہیں کرتی۔"</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"یہ ایپ صرف 1 ونڈو میں کھولی جا سکتی ہے۔"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ممکن ہے ایپ ثانوی ڈسپلے پر کام نہ کرے۔"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ایپ ثانوی ڈسپلیز پر شروعات کا تعاون نہیں کرتی۔"</string>
<string name="accessibility_divider" msgid="703810061635792791">"سپلٹ اسکرین تقسیم کار"</string>
diff --git a/libs/WindowManager/Shell/res/values-uz/strings.xml b/libs/WindowManager/Shell/res/values-uz/strings.xml
index 923e7b2d6bb6..d0384e944248 100644
--- a/libs/WindowManager/Shell/res/values-uz/strings.xml
+++ b/libs/WindowManager/Shell/res/values-uz/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Chiqarish"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Bu ilova ekranni ikkiga ajratish rejimini dastaklamaydi."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Bu ilova ekranni bo‘lish xususiyatini qo‘llab-quvvatlamaydi."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Bu ilovani faqat 1 ta oynada ochish mumkin."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Bu ilova qo‘shimcha ekranda ishlamasligi mumkin."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Bu ilova qo‘shimcha ekranlarda ishga tushmaydi."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Ekranni ikkiga bo‘lish chizig‘i"</string>
diff --git a/libs/WindowManager/Shell/res/values-vi/strings.xml b/libs/WindowManager/Shell/res/values-vi/strings.xml
index b151d72eef30..49986b591e28 100644
--- a/libs/WindowManager/Shell/res/values-vi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-vi/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Hiện"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Ứng dụng có thể không hoạt động với tính năng chia đôi màn hình."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Ứng dụng không hỗ trợ chia đôi màn hình."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Ứng dụng này chỉ có thể mở 1 cửa sổ."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Ứng dụng có thể không hoạt động trên màn hình phụ."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Ứng dụng không hỗ trợ khởi chạy trên màn hình phụ."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Bộ chia chia đôi màn hình"</string>
diff --git a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
index 66b5a4be2967..acdb2523597d 100644
--- a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"取消隐藏"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"应用可能无法在分屏模式下正常运行。"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"应用不支持分屏。"</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"此应用只能在 1 个窗口中打开。"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"应用可能无法在辅显示屏上正常运行。"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"应用不支持在辅显示屏上启动。"</string>
<string name="accessibility_divider" msgid="703810061635792791">"分屏分隔线"</string>
diff --git a/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml b/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
index 1a2e37701298..b1a957e5c5cf 100644
--- a/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"取消保護"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"應用程式可能無法在分割畫面中運作。"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"應用程式不支援分割畫面。"</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"此應用程式只可在 1 個視窗中開啟"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"應用程式可能無法在次要顯示屏上運作。"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"應用程式無法在次要顯示屏上啟動。"</string>
<string name="accessibility_divider" msgid="703810061635792791">"分割畫面分隔線"</string>
diff --git a/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml b/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml
index 99a79cfa228f..bb3dba17abc7 100644
--- a/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"取消暫時隱藏"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"應用程式可能無法在分割畫面中運作。"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"這個應用程式不支援分割畫面。"</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"這個應用程式只能在 1 個視窗中開啟。"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"應用程式可能無法在次要顯示器上運作。"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"應用程式無法在次要顯示器上啟動。"</string>
<string name="accessibility_divider" msgid="703810061635792791">"分割畫面分隔線"</string>
diff --git a/libs/WindowManager/Shell/res/values-zu/strings.xml b/libs/WindowManager/Shell/res/values-zu/strings.xml
index cfafb61b1562..51a23ff64403 100644
--- a/libs/WindowManager/Shell/res/values-zu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zu/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Susa isiteshi"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Izinhlelo zokusebenza kungenzeka zingasebenzi ngesikrini esihlukanisiwe."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Uhlelo lokusebenza alusekeli isikrini esihlukanisiwe."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Le-app ingavulwa kuphela ewindini eli-1."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Uhlelo lokusebenza kungenzeka lungasebenzi kusibonisi sesibili."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Uhlelo lokusebenza alusekeli ukuqalisa kuzibonisi zesibili."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Isihlukanisi sokuhlukanisa isikrini"</string>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
index c0a6456a2df0..164d2f149931 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
@@ -17,6 +17,7 @@
package com.android.wm.shell.activityembedding;
import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_OFFSET;
import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW;
@@ -112,23 +113,30 @@ class ActivityEmbeddingAnimationRunner {
@NonNull List<Consumer<SurfaceControl.Transaction>> postStartTransactionCallbacks) {
final List<ActivityEmbeddingAnimationAdapter> adapters = createAnimationAdapters(info,
startTransaction);
- addEdgeExtensionIfNeeded(startTransaction, finishTransaction, postStartTransactionCallbacks,
- adapters);
- addBackgroundColorIfNeeded(info, startTransaction, finishTransaction, adapters);
- long duration = 0;
- for (ActivityEmbeddingAnimationAdapter adapter : adapters) {
- duration = Math.max(duration, adapter.getDurationHint());
- }
final ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
- animator.setDuration(duration);
- animator.addUpdateListener((anim) -> {
- // Update all adapters in the same transaction.
- final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ long duration = 0;
+ if (adapters.isEmpty()) {
+ // Jump cut
+ // No need to modify the animator, but to update the startTransaction with the changes'
+ // ending states.
+ prepareForJumpCut(info, startTransaction);
+ } else {
+ addEdgeExtensionIfNeeded(startTransaction, finishTransaction,
+ postStartTransactionCallbacks, adapters);
+ addBackgroundColorIfNeeded(info, startTransaction, finishTransaction, adapters);
for (ActivityEmbeddingAnimationAdapter adapter : adapters) {
- adapter.onAnimationUpdate(t, animator.getCurrentPlayTime());
+ duration = Math.max(duration, adapter.getDurationHint());
}
- t.apply();
- });
+ animator.addUpdateListener((anim) -> {
+ // Update all adapters in the same transaction.
+ final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ for (ActivityEmbeddingAnimationAdapter adapter : adapters) {
+ adapter.onAnimationUpdate(t, animator.getCurrentPlayTime());
+ }
+ t.apply();
+ });
+ }
+ animator.setDuration(duration);
animator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {}
@@ -292,6 +300,10 @@ class ActivityEmbeddingAnimationRunner {
@NonNull
private List<ActivityEmbeddingAnimationAdapter> createChangeAnimationAdapters(
@NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction) {
+ if (shouldUseJumpCutForChangeTransition(info)) {
+ return new ArrayList<>();
+ }
+
final List<ActivityEmbeddingAnimationAdapter> adapters = new ArrayList<>();
final Set<TransitionInfo.Change> handledChanges = new ArraySet<>();
@@ -374,9 +386,11 @@ class ActivityEmbeddingAnimationRunner {
}
final Animation animation;
- if (change.getParent() != null
- && handledChanges.contains(info.getChange(change.getParent()))) {
- // No-op if it will be covered by the changing parent window.
+ if ((change.getParent() != null
+ && handledChanges.contains(info.getChange(change.getParent())))
+ || change.getMode() == TRANSIT_CHANGE) {
+ // No-op if it will be covered by the changing parent window, or it is a changing
+ // window without bounds change.
animation = ActivityEmbeddingAnimationSpec.createNoopAnimation(change);
} else if (Transitions.isClosingType(change.getMode())) {
animation = mAnimationSpec.createChangeBoundsCloseAnimation(change, parentBounds);
@@ -421,6 +435,74 @@ class ActivityEmbeddingAnimationRunner {
animationChange.getLeash(), cropBounds, Integer.MAX_VALUE);
}
+ /**
+ * Whether we should use jump cut for the change transition.
+ * This normally happens when opening a new secondary with the existing primary using a
+ * different split layout. This can be complicated, like from horizontal to vertical split with
+ * new split pairs.
+ * Uses a jump cut animation to simplify.
+ */
+ private boolean shouldUseJumpCutForChangeTransition(@NonNull TransitionInfo info) {
+ // There can be reparenting of changing Activity to new open TaskFragment, so we need to
+ // exclude both in the first iteration.
+ final List<TransitionInfo.Change> changingChanges = new ArrayList<>();
+ for (TransitionInfo.Change change : info.getChanges()) {
+ if (change.getMode() != TRANSIT_CHANGE
+ || change.getStartAbsBounds().equals(change.getEndAbsBounds())) {
+ continue;
+ }
+ changingChanges.add(change);
+ final WindowContainerToken parentToken = change.getParent();
+ if (parentToken != null) {
+ // When the parent window is also included in the transition as an opening window,
+ // we would like to animate the parent window instead.
+ final TransitionInfo.Change parentChange = info.getChange(parentToken);
+ if (parentChange != null && Transitions.isOpeningType(parentChange.getMode())) {
+ changingChanges.add(parentChange);
+ }
+ }
+ }
+ if (changingChanges.isEmpty()) {
+ // No changing target found.
+ return true;
+ }
+
+ // Check if the transition contains both opening and closing windows.
+ boolean hasOpeningWindow = false;
+ boolean hasClosingWindow = false;
+ for (TransitionInfo.Change change : info.getChanges()) {
+ if (changingChanges.contains(change)) {
+ continue;
+ }
+ if (change.getParent() != null
+ && changingChanges.contains(info.getChange(change.getParent()))) {
+ // No-op if it will be covered by the changing parent window.
+ continue;
+ }
+ hasOpeningWindow |= Transitions.isOpeningType(change.getMode());
+ hasClosingWindow |= Transitions.isClosingType(change.getMode());
+ }
+ return hasOpeningWindow && hasClosingWindow;
+ }
+
+ /** Updates the changes to end states in {@code startTransaction} for jump cut animation. */
+ private void prepareForJumpCut(@NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction) {
+ for (TransitionInfo.Change change : info.getChanges()) {
+ final SurfaceControl leash = change.getLeash();
+ startTransaction.setPosition(leash,
+ change.getEndRelOffset().x, change.getEndRelOffset().y);
+ startTransaction.setWindowCrop(leash,
+ change.getEndAbsBounds().width(), change.getEndAbsBounds().height());
+ if (change.getMode() == TRANSIT_CLOSE) {
+ startTransaction.hide(leash);
+ } else {
+ startTransaction.show(leash);
+ startTransaction.setAlpha(leash, 1f);
+ }
+ }
+ }
+
/** To provide an {@link Animation} based on the transition infos. */
private interface AnimationProvider {
Animation get(@NonNull TransitionInfo info, @NonNull TransitionInfo.Change change,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index 0133f6b44d2b..57a0fd593551 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -47,6 +47,7 @@ import android.view.MotionEvent;
import android.view.RemoteAnimationTarget;
import android.window.BackAnimationAdapter;
import android.window.BackEvent;
+import android.window.BackMotionEvent;
import android.window.BackNavigationInfo;
import android.window.IBackAnimationFinishedCallback;
import android.window.IBackAnimationRunner;
@@ -385,7 +386,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
return;
}
- final BackEvent backEvent = mTouchTracker.createProgressEvent();
+ final BackMotionEvent backEvent = mTouchTracker.createProgressEvent();
dispatchOnBackProgressed(mActiveCallback, backEvent);
}
@@ -415,7 +416,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
}
private void dispatchOnBackStarted(IOnBackInvokedCallback callback,
- BackEvent backEvent) {
+ BackMotionEvent backEvent) {
if (callback == null) {
return;
}
@@ -453,7 +454,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
}
private void dispatchOnBackProgressed(IOnBackInvokedCallback callback,
- BackEvent backEvent) {
+ BackMotionEvent backEvent) {
if (callback == null) {
return;
}
@@ -466,6 +467,11 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
}
}
+ private boolean shouldDispatchAnimation(IOnBackInvokedCallback callback) {
+ // TODO(b/258698745): Only dispatch to animation callbacks.
+ return mEnableAnimations.get();
+ }
+
/**
* Sets to true when the back gesture has passed the triggering threshold, false otherwise.
*/
@@ -640,7 +646,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
if (!mBackGestureStarted) {
// if the down -> up gesture happened before animation start, we have to
// trigger the uninterruptible transition to finish the back animation.
- final BackEvent backFinish = mTouchTracker.createProgressEvent();
+ final BackMotionEvent backFinish = mTouchTracker.createProgressEvent();
dispatchOnBackProgressed(mActiveCallback, backFinish);
startPostCommitAnimation();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java
index 9f6bc5d89e10..e36e16c82da6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java
@@ -39,6 +39,7 @@ import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import android.window.BackEvent;
+import android.window.BackMotionEvent;
import android.window.BackProgressAnimator;
import android.window.IOnBackInvokedCallback;
@@ -315,13 +316,13 @@ class CrossActivityAnimation {
private final class Callback extends IOnBackInvokedCallback.Default {
@Override
- public void onBackStarted(BackEvent backEvent) {
+ public void onBackStarted(BackMotionEvent backEvent) {
mProgressAnimator.onBackStarted(backEvent,
CrossActivityAnimation.this::onGestureProgress);
}
@Override
- public void onBackProgressed(@NonNull BackEvent backEvent) {
+ public void onBackProgressed(@NonNull BackMotionEvent backEvent) {
mProgressAnimator.onBackProgressed(backEvent);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
index a9a7b7742d2f..676e25923301 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
@@ -39,6 +39,7 @@ import android.view.SurfaceControl;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.Interpolator;
import android.window.BackEvent;
+import android.window.BackMotionEvent;
import android.window.BackProgressAnimator;
import android.window.IOnBackInvokedCallback;
@@ -316,13 +317,13 @@ class CrossTaskBackAnimation {
private final class Callback extends IOnBackInvokedCallback.Default {
@Override
- public void onBackStarted(BackEvent backEvent) {
+ public void onBackStarted(BackMotionEvent backEvent) {
mProgressAnimator.onBackStarted(backEvent,
CrossTaskBackAnimation.this::onGestureProgress);
}
@Override
- public void onBackProgressed(@NonNull BackEvent backEvent) {
+ public void onBackProgressed(@NonNull BackMotionEvent backEvent) {
mProgressAnimator.onBackProgressed(backEvent);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java
index ccfac65d6342..695ef4e66302 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java
@@ -19,6 +19,7 @@ package com.android.wm.shell.back;
import android.os.SystemProperties;
import android.view.RemoteAnimationTarget;
import android.window.BackEvent;
+import android.window.BackMotionEvent;
/**
* Helper class to record the touch location for gesture and generate back events.
@@ -82,11 +83,11 @@ class TouchTracker {
mSwipeEdge = BackEvent.EDGE_LEFT;
}
- BackEvent createStartEvent(RemoteAnimationTarget target) {
- return new BackEvent(mInitTouchX, mInitTouchY, 0, mSwipeEdge, target);
+ BackMotionEvent createStartEvent(RemoteAnimationTarget target) {
+ return new BackMotionEvent(mInitTouchX, mInitTouchY, 0, mSwipeEdge, target);
}
- BackEvent createProgressEvent() {
+ BackMotionEvent createProgressEvent() {
float progressThreshold = PROGRESS_THRESHOLD >= 0
? PROGRESS_THRESHOLD : mProgressThreshold;
progressThreshold = progressThreshold == 0 ? 1 : progressThreshold;
@@ -109,8 +110,8 @@ class TouchTracker {
return createProgressEvent(progress);
}
- BackEvent createProgressEvent(float progress) {
- return new BackEvent(mLatestTouchX, mLatestTouchY, progress, mSwipeEdge, null);
+ BackMotionEvent createProgressEvent(float progress) {
+ return new BackMotionEvent(mLatestTouchX, mLatestTouchY, progress, mSwipeEdge, null);
}
public void setProgressThreshold(float progressThreshold) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index f621351907d2..04d62f63c398 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -1955,6 +1955,7 @@ public class BubbleStackView extends FrameLayout
if (wasExpanded) {
stopMonitoringSwipeUpGesture();
animateCollapse();
+ showManageMenu(false);
logBubbleEvent(mExpandedBubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__COLLAPSED);
} else {
animateExpansion();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
index 6e116b958ac9..c836b95ffab8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
@@ -51,8 +51,6 @@ import com.android.wm.shell.R;
import com.android.wm.shell.common.ScreenshotUtils;
import com.android.wm.shell.common.SurfaceUtils;
-import java.util.function.Consumer;
-
/**
* Handles split decor like showing resizing hint for a specific split.
*/
@@ -72,17 +70,18 @@ public class SplitDecorManager extends WindowlessWindowManager {
private SurfaceControl mIconLeash;
private SurfaceControl mBackgroundLeash;
private SurfaceControl mGapBackgroundLeash;
+ private SurfaceControl mScreenshot;
private boolean mShown;
private boolean mIsResizing;
private final Rect mBounds = new Rect();
- private final Rect mResizingBounds = new Rect();
private final Rect mTempRect = new Rect();
private ValueAnimator mFadeAnimator;
private int mIconSize;
private int mOffsetX;
private int mOffsetY;
+ private int mRunningAnimationCount = 0;
public SplitDecorManager(Configuration configuration, IconProvider iconProvider,
SurfaceSession surfaceSession) {
@@ -173,7 +172,6 @@ public class SplitDecorManager extends WindowlessWindowManager {
mIsResizing = true;
mBounds.set(newBounds);
}
- mResizingBounds.set(newBounds);
mOffsetX = offsetX;
mOffsetY = offsetY;
@@ -227,33 +225,41 @@ public class SplitDecorManager extends WindowlessWindowManager {
t.setVisibility(mBackgroundLeash, show);
t.setVisibility(mIconLeash, show);
} else {
- startFadeAnimation(show, null /* finishedConsumer */);
+ startFadeAnimation(show, false, null);
}
mShown = show;
}
}
/** Stops showing resizing hint. */
- public void onResized(SurfaceControl.Transaction t) {
- if (!mShown && mIsResizing) {
- mTempRect.set(mResizingBounds);
- mTempRect.offsetTo(-mOffsetX, -mOffsetY);
- final SurfaceControl screenshot = ScreenshotUtils.takeScreenshot(t,
- mHostLeash, mTempRect, Integer.MAX_VALUE - 1);
+ public void onResized(SurfaceControl.Transaction t, Runnable animFinishedCallback) {
+ if (mScreenshot != null) {
+ t.setPosition(mScreenshot, mOffsetX, mOffsetY);
final SurfaceControl.Transaction animT = new SurfaceControl.Transaction();
final ValueAnimator va = ValueAnimator.ofFloat(1, 0);
va.addUpdateListener(valueAnimator -> {
final float progress = (float) valueAnimator.getAnimatedValue();
- animT.setAlpha(screenshot, progress);
+ animT.setAlpha(mScreenshot, progress);
animT.apply();
});
va.addListener(new AnimatorListenerAdapter() {
@Override
+ public void onAnimationStart(Animator animation) {
+ mRunningAnimationCount++;
+ }
+
+ @Override
public void onAnimationEnd(@androidx.annotation.NonNull Animator animation) {
- animT.remove(screenshot);
+ mRunningAnimationCount--;
+ animT.remove(mScreenshot);
animT.apply();
animT.close();
+ mScreenshot = null;
+
+ if (mRunningAnimationCount == 0 && animFinishedCallback != null) {
+ animFinishedCallback.run();
+ }
}
});
va.start();
@@ -285,10 +291,34 @@ public class SplitDecorManager extends WindowlessWindowManager {
mFadeAnimator.cancel();
}
if (mShown) {
- fadeOutDecor(null /* finishedCallback */);
+ fadeOutDecor(animFinishedCallback);
} else {
// Decor surface is hidden so release it directly.
releaseDecor(t);
+ if (mRunningAnimationCount == 0 && animFinishedCallback != null) {
+ animFinishedCallback.run();
+ }
+ }
+ }
+
+ /** Screenshot host leash and attach on it if meet some conditions */
+ public void screenshotIfNeeded(SurfaceControl.Transaction t) {
+ if (!mShown && mIsResizing) {
+ mTempRect.set(mBounds);
+ mTempRect.offsetTo(0, 0);
+ mScreenshot = ScreenshotUtils.takeScreenshot(t, mHostLeash, mTempRect,
+ Integer.MAX_VALUE - 1);
+ }
+ }
+
+ /** Set screenshot and attach on host leash it if meet some conditions */
+ public void setScreenshotIfNeeded(SurfaceControl screenshot, SurfaceControl.Transaction t) {
+ if (screenshot == null || !screenshot.isValid()) return;
+
+ if (!mShown && mIsResizing) {
+ mScreenshot = screenshot;
+ t.reparent(screenshot, mHostLeash);
+ t.setLayer(screenshot, Integer.MAX_VALUE - 1);
}
}
@@ -296,18 +326,15 @@ public class SplitDecorManager extends WindowlessWindowManager {
* directly. */
public void fadeOutDecor(Runnable finishedCallback) {
if (mShown) {
- startFadeAnimation(false /* show */, transaction -> {
- releaseDecor(transaction);
- if (finishedCallback != null) finishedCallback.run();
- });
+ startFadeAnimation(false /* show */, true, finishedCallback);
mShown = false;
} else {
if (finishedCallback != null) finishedCallback.run();
}
}
- private void startFadeAnimation(boolean show,
- Consumer<SurfaceControl.Transaction> finishedConsumer) {
+ private void startFadeAnimation(boolean show, boolean releaseSurface,
+ Runnable finishedCallback) {
final SurfaceControl.Transaction animT = new SurfaceControl.Transaction();
mFadeAnimator = ValueAnimator.ofFloat(0f, 1f);
mFadeAnimator.setDuration(FADE_DURATION);
@@ -324,6 +351,7 @@ public class SplitDecorManager extends WindowlessWindowManager {
mFadeAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(@NonNull Animator animation) {
+ mRunningAnimationCount++;
if (show) {
animT.show(mBackgroundLeash).show(mIconLeash);
}
@@ -335,6 +363,7 @@ public class SplitDecorManager extends WindowlessWindowManager {
@Override
public void onAnimationEnd(@NonNull Animator animation) {
+ mRunningAnimationCount--;
if (!show) {
if (mBackgroundLeash != null) {
animT.hide(mBackgroundLeash);
@@ -343,11 +372,15 @@ public class SplitDecorManager extends WindowlessWindowManager {
animT.hide(mIconLeash);
}
}
- if (finishedConsumer != null) {
- finishedConsumer.accept(animT);
+ if (releaseSurface) {
+ releaseDecor(animT);
}
animT.apply();
animT.close();
+
+ if (mRunningAnimationCount == 0 && finishedCallback != null) {
+ finishedCallback.run();
+ }
}
});
mFadeAnimator.start();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 4ea8a5dbc5a9..661c08b18f46 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -697,10 +697,13 @@ public abstract class WMShellBaseModule {
@WMSingleton
@Provides
- static Optional<DesktopModeController> providesDesktopModeController(
- @DynamicOverride Optional<DesktopModeController> desktopModeController) {
- if (DesktopModeStatus.IS_SUPPORTED) {
- return desktopModeController;
+ static Optional<DesktopModeController> provideDesktopModeController(
+ @DynamicOverride Optional<Lazy<DesktopModeController>> desktopModeController) {
+ // Use optional-of-lazy for the dependency that this provider relies on.
+ // Lazy ensures that this provider will not be the cause the dependency is created
+ // when it will not be returned due to the condition below.
+ if (DesktopModeStatus.isProto1Enabled()) {
+ return desktopModeController.map(Lazy::get);
}
return Optional.empty();
}
@@ -711,10 +714,13 @@ public abstract class WMShellBaseModule {
@WMSingleton
@Provides
- static Optional<DesktopModeTaskRepository> providesDesktopTaskRepository(
- @DynamicOverride Optional<DesktopModeTaskRepository> desktopModeTaskRepository) {
- if (DesktopModeStatus.IS_SUPPORTED) {
- return desktopModeTaskRepository;
+ static Optional<DesktopModeTaskRepository> provideDesktopTaskRepository(
+ @DynamicOverride Optional<Lazy<DesktopModeTaskRepository>> desktopModeTaskRepository) {
+ // Use optional-of-lazy for the dependency that this provider relies on.
+ // Lazy ensures that this provider will not be the cause the dependency is created
+ // when it will not be returned due to the condition below.
+ if (DesktopModeStatus.isAnyEnabled()) {
+ return desktopModeTaskRepository.map(Lazy::get);
}
return Optional.empty();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index f1670cd792cf..6be83054ae59 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -189,7 +189,7 @@ public abstract class WMShellModule {
ShellTaskOrganizer taskOrganizer,
DisplayController displayController,
SyncTransactionQueue syncQueue,
- @DynamicOverride DesktopModeController desktopModeController) {
+ Optional<DesktopModeController> desktopModeController) {
return new CaptionWindowDecorViewModel(
context,
mainHandler,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
index abc4024bc290..7eb01a79f14f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
@@ -64,6 +64,7 @@ import com.android.wm.shell.transition.Transitions;
import java.util.ArrayList;
import java.util.Comparator;
+import java.util.List;
import java.util.concurrent.Executor;
/**
@@ -99,7 +100,9 @@ public class DesktopModeController implements RemoteCallable<DesktopModeControll
mDesktopModeTaskRepository = desktopModeTaskRepository;
mMainExecutor = mainExecutor;
mSettingsObserver = new SettingsObserver(mContext, mainHandler);
- shellInit.addInitCallback(this::onInit, this);
+ if (DesktopModeStatus.isProto1Enabled()) {
+ shellInit.addInitCallback(this::onInit, this);
+ }
}
private void onInit() {
@@ -258,18 +261,36 @@ public class DesktopModeController implements RemoteCallable<DesktopModeControll
@NonNull
private WindowContainerTransaction bringDesktopAppsToFront() {
- ArraySet<Integer> activeTasks = mDesktopModeTaskRepository.getActiveTasks();
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ final ArraySet<Integer> activeTasks = mDesktopModeTaskRepository.getActiveTasks();
ProtoLog.d(WM_SHELL_DESKTOP_MODE, "bringDesktopAppsToFront: tasks=%s", activeTasks.size());
- ArrayList<RunningTaskInfo> taskInfos = new ArrayList<>();
+
+ final List<RunningTaskInfo> taskInfos = new ArrayList<>();
for (Integer taskId : activeTasks) {
RunningTaskInfo taskInfo = mShellTaskOrganizer.getRunningTaskInfo(taskId);
if (taskInfo != null) {
taskInfos.add(taskInfo);
}
}
- // Order by lastActiveTime, descending
- taskInfos.sort(Comparator.comparingLong(task -> -task.lastActiveTime));
- WindowContainerTransaction wct = new WindowContainerTransaction();
+
+ if (taskInfos.isEmpty()) {
+ return wct;
+ }
+
+ final boolean allActiveTasksAreVisible = taskInfos.stream()
+ .allMatch(info -> mDesktopModeTaskRepository.isVisibleTask(info.taskId));
+ if (allActiveTasksAreVisible) {
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE,
+ "bringDesktopAppsToFront: active tasks are already in front, skipping.");
+ return wct;
+ }
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE,
+ "bringDesktopAppsToFront: reordering all active tasks to the front");
+ final List<Integer> allTasksInZOrder =
+ mDesktopModeTaskRepository.getFreeformTasksInZOrder();
+ // Sort by z-order, bottom to top, so that the top-most task is reordered to the top last
+ // in the WCT.
+ taskInfos.sort(Comparator.comparingInt(task -> -allTasksInZOrder.indexOf(task.taskId)));
for (RunningTaskInfo task : taskInfos) {
wct.reorder(task.token, true);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
index 2fafe67664f8..67f4a1914c49 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
@@ -33,10 +33,38 @@ public class DesktopModeStatus {
/**
* Flag to indicate whether desktop mode is available on the device
*/
- public static final boolean IS_SUPPORTED = SystemProperties.getBoolean(
+ private static final boolean IS_SUPPORTED = SystemProperties.getBoolean(
"persist.wm.debug.desktop_mode", false);
/**
+ * Flag to indicate whether desktop mode proto 2 is available on the device
+ */
+ private static final boolean IS_PROTO2_ENABLED = SystemProperties.getBoolean(
+ "persist.wm.debug.desktop_mode_2", false);
+
+ /**
+ * Return {@code true} if desktop mode support is enabled
+ */
+ public static boolean isProto1Enabled() {
+ return IS_SUPPORTED;
+ }
+
+ /**
+ * Return {@code true} is desktop windowing proto 2 is enabled
+ */
+ public static boolean isProto2Enabled() {
+ return IS_PROTO2_ENABLED;
+ }
+
+ /**
+ * Return {@code true} if proto 1 or 2 is enabled.
+ * Can be used to guard logic that is common for both prototypes.
+ */
+ public static boolean isAnyEnabled() {
+ return isProto1Enabled() || isProto2Enabled();
+ }
+
+ /**
* Check if desktop mode is active
*
* @return {@code true} if active
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
index b7749fc4c3d4..600ccc17ecaa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
@@ -33,6 +33,8 @@ class DesktopModeTaskRepository {
*/
private val activeTasks = ArraySet<Int>()
private val visibleTasks = ArraySet<Int>()
+ // Tasks currently in freeform mode, ordered from top to bottom (top is at index 0).
+ private val freeformTasksInZOrder = mutableListOf<Int>()
private val activeTasksListeners = ArraySet<ActiveTasksListener>()
// Track visible tasks separately because a task may be part of the desktop but not visible.
private val visibleTasksListeners = ArrayMap<VisibleTasksListener, Executor>()
@@ -101,6 +103,13 @@ class DesktopModeTaskRepository {
}
/**
+ * Whether a task is visible.
+ */
+ fun isVisibleTask(taskId: Int): Boolean {
+ return visibleTasks.contains(taskId)
+ }
+
+ /**
* Get a set of the active tasks
*/
fun getActiveTasks(): ArraySet<Int> {
@@ -108,6 +117,13 @@ class DesktopModeTaskRepository {
}
/**
+ * Get a list of freeform tasks, ordered from top-bottom (top at index 0).
+ */
+ fun getFreeformTasksInZOrder(): List<Int> {
+ return freeformTasksInZOrder
+ }
+
+ /**
* Updates whether a freeform task with this id is visible or not and notifies listeners.
*/
fun updateVisibleFreeformTasks(taskId: Int, visible: Boolean) {
@@ -127,6 +143,23 @@ class DesktopModeTaskRepository {
}
/**
+ * Add (or move if it already exists) the task to the top of the ordered list.
+ */
+ fun addOrMoveFreeformTaskToTop(taskId: Int) {
+ if (freeformTasksInZOrder.contains(taskId)) {
+ freeformTasksInZOrder.remove(taskId)
+ }
+ freeformTasksInZOrder.add(0, taskId)
+ }
+
+ /**
+ * Remove the task from the ordered list.
+ */
+ fun removeFreeformTask(taskId: Int) {
+ freeformTasksInZOrder.remove(taskId)
+ }
+
+ /**
* Defines interface for classes that can listen to changes for active tasks in desktop mode.
*/
interface ActiveTasksListener {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS
new file mode 100644
index 000000000000..926cfb3b12ef
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS
@@ -0,0 +1,2 @@
+# WM shell sub-module desktop owners
+madym@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
index 44bcdb2d5de5..793bad86d873 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
@@ -38,7 +38,8 @@ import java.util.Optional;
* {@link ShellTaskOrganizer.TaskListener} for {@link
* ShellTaskOrganizer#TASK_LISTENER_TYPE_FREEFORM}.
*/
-public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener {
+public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener,
+ ShellTaskOrganizer.FocusListener {
private static final String TAG = "FreeformTaskListener";
private final ShellTaskOrganizer mShellTaskOrganizer;
@@ -67,6 +68,9 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener {
private void onInit() {
mShellTaskOrganizer.addListenerForType(this, TASK_LISTENER_TYPE_FREEFORM);
+ if (DesktopModeStatus.isAnyEnabled()) {
+ mShellTaskOrganizer.addFocusListener(this);
+ }
}
@Override
@@ -86,13 +90,16 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener {
t.apply();
}
- if (DesktopModeStatus.IS_SUPPORTED && taskInfo.isVisible) {
+ if (DesktopModeStatus.isAnyEnabled()) {
mDesktopModeTaskRepository.ifPresent(repository -> {
- if (repository.addActiveTask(taskInfo.taskId)) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
- "Adding active freeform task: #%d", taskInfo.taskId);
+ repository.addOrMoveFreeformTaskToTop(taskInfo.taskId);
+ if (taskInfo.isVisible) {
+ if (repository.addActiveTask(taskInfo.taskId)) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
+ "Adding active freeform task: #%d", taskInfo.taskId);
+ }
+ repository.updateVisibleFreeformTasks(taskInfo.taskId, true);
}
- repository.updateVisibleFreeformTasks(taskInfo.taskId, true);
});
}
}
@@ -103,8 +110,9 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener {
taskInfo.taskId);
mTasks.remove(taskInfo.taskId);
- if (DesktopModeStatus.IS_SUPPORTED) {
+ if (DesktopModeStatus.isAnyEnabled()) {
mDesktopModeTaskRepository.ifPresent(repository -> {
+ repository.removeFreeformTask(taskInfo.taskId);
if (repository.removeActiveTask(taskInfo.taskId)) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
"Removing active freeform task: #%d", taskInfo.taskId);
@@ -126,7 +134,7 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener {
taskInfo.taskId);
mWindowDecorationViewModel.onTaskInfoChanged(state.mTaskInfo);
- if (DesktopModeStatus.IS_SUPPORTED) {
+ if (DesktopModeStatus.isAnyEnabled()) {
mDesktopModeTaskRepository.ifPresent(repository -> {
if (taskInfo.isVisible) {
if (repository.addActiveTask(taskInfo.taskId)) {
@@ -140,6 +148,18 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener {
}
@Override
+ public void onFocusTaskChanged(RunningTaskInfo taskInfo) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG,
+ "Freeform Task Focus Changed: #%d focused=%b",
+ taskInfo.taskId, taskInfo.isFocused);
+ if (DesktopModeStatus.isAnyEnabled() && taskInfo.isFocused) {
+ mDesktopModeTaskRepository.ifPresent(repository -> {
+ repository.addOrMoveFreeformTaskToTop(taskInfo.taskId);
+ });
+ }
+ }
+
+ @Override
public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) {
b.setParent(findTaskSurface(taskId));
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
index 6e710f7caeda..60e5ff27cab9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
@@ -90,7 +90,7 @@ public class FreeformTaskTransitionObserver implements Transitions.TransitionObs
// This logic relies on 2 assumptions: 1 is that child tasks will be visited before
// parents (due to how z-order works). 2 is that no non-tasks are interleaved
// between tasks (hierarchically).
- taskParents.add(change.getContainer());
+ taskParents.add(change.getParent());
}
if (taskParents.contains(change.getContainer())) {
continue;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/OWNERS
new file mode 100644
index 000000000000..0c2d5c49f830
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/OWNERS
@@ -0,0 +1,2 @@
+# WM shell sub-module freeform owners
+madym@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
index 17d7f5d0d567..5376ae372de2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
@@ -97,6 +97,8 @@ public class PipBoundsState {
private int mShelfHeight;
/** Whether the user has resized the PIP manually. */
private boolean mHasUserResizedPip;
+ /** Whether the user has moved the PIP manually. */
+ private boolean mHasUserMovedPip;
/**
* Areas defined by currently visible apps that they prefer to keep clear from overlays such as
* the PiP. Restricted areas may only move the PiP a limited amount from its anchor position.
@@ -279,6 +281,7 @@ public class PipBoundsState {
if (changed) {
clearReentryState();
setHasUserResizedPip(false);
+ setHasUserMovedPip(false);
}
}
@@ -442,6 +445,16 @@ public class PipBoundsState {
mHasUserResizedPip = hasUserResizedPip;
}
+ /** Returns whether the user has moved the PIP. */
+ public boolean hasUserMovedPip() {
+ return mHasUserMovedPip;
+ }
+
+ /** Set whether the user has moved the PIP. */
+ public void setHasUserMovedPip(boolean hasUserMovedPip) {
+ mHasUserMovedPip = hasUserMovedPip;
+ }
+
/**
* Registers a callback when the minimal size of PIP that is set by the app changes.
*/
@@ -577,6 +590,8 @@ public class PipBoundsState {
pw.println(innerPrefix + "mImeHeight=" + mImeHeight);
pw.println(innerPrefix + "mIsShelfShowing=" + mIsShelfShowing);
pw.println(innerPrefix + "mShelfHeight=" + mShelfHeight);
+ pw.println(innerPrefix + "mHasUserMovedPip=" + mHasUserMovedPip);
+ pw.println(innerPrefix + "mHasUserResizedPip=" + mHasUserResizedPip);
if (mPipReentryState == null) {
pw.println(innerPrefix + "mPipReentryState=null");
} else {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java
index 84071e08d472..690505e03fce 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java
@@ -19,6 +19,7 @@ package com.android.wm.shell.pip.phone;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Rect;
+import android.os.SystemProperties;
import android.util.ArraySet;
import android.view.Gravity;
@@ -34,6 +35,10 @@ import java.util.Set;
*/
public class PhonePipKeepClearAlgorithm implements PipKeepClearAlgorithm {
+ private boolean mKeepClearAreaGravityEnabled =
+ SystemProperties.getBoolean(
+ "persist.wm.debug.enable_pip_keep_clear_algorithm_gravity", false);
+
protected int mKeepClearAreasPadding;
public PhonePipKeepClearAlgorithm(Context context) {
@@ -53,31 +58,36 @@ public class PhonePipKeepClearAlgorithm implements PipKeepClearAlgorithm {
Rect startingBounds = pipBoundsState.getBounds().isEmpty()
? pipBoundsAlgorithm.getEntryDestinationBoundsIgnoringKeepClearAreas()
: pipBoundsState.getBounds();
- float snapFraction = pipBoundsAlgorithm.getSnapFraction(startingBounds);
- int verticalGravity = Gravity.BOTTOM;
- int horizontalGravity;
- if (snapFraction >= 0.5f && snapFraction < 2.5f) {
- horizontalGravity = Gravity.RIGHT;
- } else {
- horizontalGravity = Gravity.LEFT;
- }
- // push the bounds based on the gravity
Rect insets = new Rect();
pipBoundsAlgorithm.getInsetBounds(insets);
if (pipBoundsState.isImeShowing()) {
insets.bottom -= pipBoundsState.getImeHeight();
}
- Rect pushedBounds = new Rect(startingBounds);
- if (verticalGravity == Gravity.BOTTOM) {
- pushedBounds.offsetTo(pushedBounds.left,
- insets.bottom - pushedBounds.height());
- }
- if (horizontalGravity == Gravity.RIGHT) {
- pushedBounds.offsetTo(insets.right - pushedBounds.width(), pushedBounds.top);
- } else {
- pushedBounds.offsetTo(insets.left, pushedBounds.top);
+ Rect pipBounds = new Rect(startingBounds);
+
+ // move PiP towards corner if user hasn't moved it manually or the flag is on
+ if (mKeepClearAreaGravityEnabled
+ || (!pipBoundsState.hasUserMovedPip() && !pipBoundsState.hasUserResizedPip())) {
+ float snapFraction = pipBoundsAlgorithm.getSnapFraction(startingBounds);
+ int verticalGravity = Gravity.BOTTOM;
+ int horizontalGravity;
+ if (snapFraction >= 0.5f && snapFraction < 2.5f) {
+ horizontalGravity = Gravity.RIGHT;
+ } else {
+ horizontalGravity = Gravity.LEFT;
+ }
+ if (verticalGravity == Gravity.BOTTOM) {
+ pipBounds.offsetTo(pipBounds.left,
+ insets.bottom - pipBounds.height());
+ }
+ if (horizontalGravity == Gravity.RIGHT) {
+ pipBounds.offsetTo(insets.right - pipBounds.width(), pipBounds.top);
+ } else {
+ pipBounds.offsetTo(insets.left, pipBounds.top);
+ }
}
- return findUnoccludedPosition(pushedBounds, pipBoundsState.getRestrictedKeepClearAreas(),
+
+ return findUnoccludedPosition(pipBounds, pipBoundsState.getRestrictedKeepClearAreas(),
pipBoundsState.getUnrestrictedKeepClearAreas(), insets);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
index a9a97beb9180..83bc7c0e6e7d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
@@ -875,6 +875,8 @@ public class PipTouchHandler {
}
if (touchState.isDragging()) {
+ mPipBoundsState.setHasUserMovedPip(true);
+
// Move the pinned stack freely
final PointF lastDelta = touchState.getLastTouchDelta();
float lastX = mStartPosition.x + mDelta.x;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index f9172ba183de..db0f0bf6fda8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -342,6 +342,16 @@ public class RecentTasksController implements TaskStackListenerCallback,
}
/**
+ * Returns the top running leaf task.
+ */
+ @Nullable
+ public ActivityManager.RunningTaskInfo getTopRunningTask() {
+ List<ActivityManager.RunningTaskInfo> tasks = mActivityTaskManager.getTasks(1,
+ false /* filterOnlyVisibleRecents */);
+ return tasks.isEmpty() ? null : tasks.get(0);
+ }
+
+ /**
* Find the background task that match the given component.
*/
@Nullable
@@ -367,6 +377,8 @@ public class RecentTasksController implements TaskStackListenerCallback,
public void dump(@NonNull PrintWriter pw, String prefix) {
final String innerPrefix = prefix + " ";
pw.println(prefix + TAG);
+ pw.println(prefix + " mListener=" + mListener);
+ pw.println(prefix + "Tasks:");
ArrayList<GroupedRecentTaskInfo> recentTasks = getRecentTasks(Integer.MAX_VALUE,
ActivityManager.RECENT_IGNORE_UNAVAILABLE, ActivityManager.getCurrentUser());
for (int i = 0; i < recentTasks.size(); i++) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
index d86aadc996e3..2f2bc77b804b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
@@ -73,6 +73,9 @@ public interface SplitScreen {
/** Called when device waking up finished. */
void onFinishedWakingUp();
+ /** Called when requested to go to fullscreen from the current active split app. */
+ void goToFullscreenFromSplit();
+
/** Get a string representation of a stage type */
static String stageTypeToString(@StageType int stage) {
switch (stage) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index a79ac45228ca..9329d021d007 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -123,6 +123,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
public static final int EXIT_REASON_SCREEN_LOCKED = 7;
public static final int EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP = 8;
public static final int EXIT_REASON_CHILD_TASK_ENTER_PIP = 9;
+ public static final int EXIT_REASON_FULLSCREEN_SHORTCUT = 10;
@IntDef(value = {
EXIT_REASON_UNKNOWN,
EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW,
@@ -134,6 +135,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
EXIT_REASON_SCREEN_LOCKED,
EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP,
EXIT_REASON_CHILD_TASK_ENTER_PIP,
+ EXIT_REASON_FULLSCREEN_SHORTCUT,
})
@Retention(RetentionPolicy.SOURCE)
@interface ExitReason{}
@@ -315,10 +317,6 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
return mStageCoordinator;
}
- public ActivityManager.RunningTaskInfo getFocusingTaskInfo() {
- return mStageCoordinator.getFocusingTaskInfo();
- }
-
public boolean isValidToEnterSplitScreen(@NonNull ActivityManager.RunningTaskInfo taskInfo) {
return mStageCoordinator.isValidToEnterSplitScreen(taskInfo);
}
@@ -422,6 +420,10 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
mStageCoordinator.unregisterSplitScreenListener(listener);
}
+ public void goToFullscreenFromSplit() {
+ mStageCoordinator.goToFullscreenFromSplit();
+ }
+
public void startTask(int taskId, @SplitPosition int position, @Nullable Bundle options) {
final int[] result = new int[1];
IRemoteAnimationRunner wrapper = new IRemoteAnimationRunner.Stub() {
@@ -527,10 +529,24 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
@SplitPosition int splitPosition, float splitRatio, RemoteAnimationAdapter adapter,
InstanceId instanceId) {
Intent fillInIntent = null;
- if (launchSameComponentAdjacently(pendingIntent, splitPosition, taskId)
- && supportMultiInstancesSplit(pendingIntent.getIntent().getComponent())) {
- fillInIntent = new Intent();
- fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+ if (launchSameComponentAdjacently(pendingIntent, splitPosition, taskId)) {
+ if (supportMultiInstancesSplit(pendingIntent.getIntent().getComponent())) {
+ fillInIntent = new Intent();
+ fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
+ } else {
+ try {
+ adapter.getRunner().onAnimationCancelled(false /* isKeyguardOccluded */);
+ ActivityTaskManager.getService().startActivityFromRecents(taskId, options2);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error starting remote animation", e);
+ }
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+ "Cancel entering split as not supporting multi-instances");
+ Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
+ Toast.LENGTH_SHORT).show();
+ return;
+ }
}
mStageCoordinator.startIntentAndTaskWithLegacyTransition(pendingIntent, fillInIntent,
options1, taskId, options2, splitPosition, splitRatio, adapter, instanceId);
@@ -540,10 +556,17 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition,
float splitRatio, @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
Intent fillInIntent = null;
- if (launchSameComponentAdjacently(pendingIntent, splitPosition, taskId)
- && supportMultiInstancesSplit(pendingIntent.getIntent().getComponent())) {
- fillInIntent = new Intent();
- fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+ if (launchSameComponentAdjacently(pendingIntent, splitPosition, taskId)) {
+ if (supportMultiInstancesSplit(pendingIntent.getIntent().getComponent())) {
+ fillInIntent = new Intent();
+ fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
+ } else {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+ "Cancel entering split as not supporting multi-instances");
+ Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
+ Toast.LENGTH_SHORT).show();
+ }
}
mStageCoordinator.startIntentAndTask(pendingIntent, fillInIntent, options1, taskId,
options2, splitPosition, splitRatio, remoteTransition, instanceId);
@@ -555,12 +578,26 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
float splitRatio, RemoteAnimationAdapter adapter, InstanceId instanceId) {
Intent fillInIntent1 = null;
Intent fillInIntent2 = null;
- if (launchSameComponentAdjacently(pendingIntent1, pendingIntent2)
- && supportMultiInstancesSplit(pendingIntent1.getIntent().getComponent())) {
- fillInIntent1 = new Intent();
- fillInIntent1.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
- fillInIntent2 = new Intent();
- fillInIntent2.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+ if (launchSameComponentAdjacently(pendingIntent1, pendingIntent2)) {
+ if (supportMultiInstancesSplit(pendingIntent1.getIntent().getComponent())) {
+ fillInIntent1 = new Intent();
+ fillInIntent1.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+ fillInIntent2 = new Intent();
+ fillInIntent2.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
+ } else {
+ try {
+ adapter.getRunner().onAnimationCancelled(false /* isKeyguardOccluded */);
+ pendingIntent1.send();
+ } catch (RemoteException | PendingIntent.CanceledException e) {
+ Slog.e(TAG, "Error starting remote animation", e);
+ }
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+ "Cancel entering split as not supporting multi-instances");
+ Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
+ Toast.LENGTH_SHORT).show();
+ return;
+ }
}
mStageCoordinator.startIntentsWithLegacyTransition(pendingIntent1, fillInIntent1, options1,
pendingIntent2, fillInIntent2, options2, splitPosition, splitRatio, adapter,
@@ -599,6 +636,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
mStageCoordinator.switchSplitPosition("startIntent");
return;
} else {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+ "Cancel entering split as not supporting multi-instances");
Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
Toast.LENGTH_SHORT).show();
return;
@@ -628,9 +667,10 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
if (!isSplitScreenVisible()) {
// Split screen is not yet activated, check if the current top running task is valid to
// split together.
- final ActivityManager.RunningTaskInfo taskInfo = getFocusingTaskInfo();
- if (taskInfo != null && isValidToEnterSplitScreen(taskInfo)) {
- return Objects.equals(taskInfo.baseIntent.getComponent(), launchingActivity);
+ final ActivityManager.RunningTaskInfo topRunningTask = mRecentTasksOptional
+ .map(recentTasks -> recentTasks.getTopRunningTask()).orElse(null);
+ if (topRunningTask != null && isValidToEnterSplitScreen(topRunningTask)) {
+ return Objects.equals(topRunningTask.baseIntent.getComponent(), launchingActivity);
}
return false;
}
@@ -863,9 +903,12 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
@Override
public void onFinishedWakingUp() {
- mMainExecutor.execute(() -> {
- SplitScreenController.this.onFinishedWakingUp();
- });
+ mMainExecutor.execute(SplitScreenController.this::onFinishedWakingUp);
+ }
+
+ @Override
+ public void goToFullscreenFromSplit() {
+ mMainExecutor.execute(SplitScreenController.this::goToFullscreenFromSplit);
}
}
@@ -921,33 +964,25 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
@Override
public void exitSplitScreen(int toTopTaskId) {
executeRemoteCallWithTaskPermission(mController, "exitSplitScreen",
- (controller) -> {
- controller.exitSplitScreen(toTopTaskId, EXIT_REASON_UNKNOWN);
- });
+ (controller) -> controller.exitSplitScreen(toTopTaskId, EXIT_REASON_UNKNOWN));
}
@Override
public void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) {
executeRemoteCallWithTaskPermission(mController, "exitSplitScreenOnHide",
- (controller) -> {
- controller.exitSplitScreenOnHide(exitSplitScreenOnHide);
- });
+ (controller) -> controller.exitSplitScreenOnHide(exitSplitScreenOnHide));
}
@Override
public void removeFromSideStage(int taskId) {
executeRemoteCallWithTaskPermission(mController, "removeFromSideStage",
- (controller) -> {
- controller.removeFromSideStage(taskId);
- });
+ (controller) -> controller.removeFromSideStage(taskId));
}
@Override
public void startTask(int taskId, int position, @Nullable Bundle options) {
executeRemoteCallWithTaskPermission(mController, "startTask",
- (controller) -> {
- controller.startTask(taskId, position, options);
- });
+ (controller) -> controller.startTask(taskId, position, options));
}
@Override
@@ -1039,19 +1074,16 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
public void startShortcut(String packageName, String shortcutId, int position,
@Nullable Bundle options, UserHandle user, InstanceId instanceId) {
executeRemoteCallWithTaskPermission(mController, "startShortcut",
- (controller) -> {
- controller.startShortcut(packageName, shortcutId, position, options, user,
- instanceId);
- });
+ (controller) -> controller.startShortcut(packageName, shortcutId, position,
+ options, user, instanceId));
}
@Override
public void startIntent(PendingIntent intent, Intent fillInIntent, int position,
@Nullable Bundle options, InstanceId instanceId) {
executeRemoteCallWithTaskPermission(mController, "startIntent",
- (controller) -> {
- controller.startIntent(intent, fillInIntent, position, options, instanceId);
- });
+ (controller) -> controller.startIntent(intent, fillInIntent, position, options,
+ instanceId));
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
index 21a13103616c..1cf3a896b68e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
@@ -47,6 +47,7 @@ import android.window.WindowContainerTransactionCallback;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.common.split.SplitDecorManager;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.transition.OneShotRemoteHandler;
import com.android.wm.shell.transition.Transitions;
@@ -64,6 +65,7 @@ class SplitScreenTransitions {
DismissTransition mPendingDismiss = null;
TransitSession mPendingEnter = null;
TransitSession mPendingRecent = null;
+ TransitSession mPendingResize = null;
private IBinder mAnimatingTransition = null;
OneShotRemoteHandler mPendingRemoteHandler = null;
@@ -177,6 +179,43 @@ class SplitScreenTransitions {
onFinish(null /* wct */, null /* wctCB */);
}
+ void applyResizeTransition(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback,
+ @NonNull WindowContainerToken mainRoot, @NonNull WindowContainerToken sideRoot,
+ @NonNull SplitDecorManager mainDecor, @NonNull SplitDecorManager sideDecor) {
+ mFinishCallback = finishCallback;
+ mAnimatingTransition = transition;
+ mFinishTransaction = finishTransaction;
+
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+ if (mainRoot.equals(change.getContainer()) || sideRoot.equals(change.getContainer())) {
+ final SurfaceControl leash = change.getLeash();
+ startTransaction.setPosition(leash, change.getEndAbsBounds().left,
+ change.getEndAbsBounds().top);
+ startTransaction.setWindowCrop(leash, change.getEndAbsBounds().width(),
+ change.getEndAbsBounds().height());
+
+ SplitDecorManager decor = mainRoot.equals(change.getContainer())
+ ? mainDecor : sideDecor;
+ ValueAnimator va = new ValueAnimator();
+ mAnimations.add(va);
+ decor.setScreenshotIfNeeded(change.getSnapshot(), startTransaction);
+ decor.onResized(startTransaction, () -> {
+ mTransitions.getMainExecutor().execute(() -> {
+ mAnimations.remove(va);
+ onFinish(null /* wct */, null /* wctCB */);
+ });
+ });
+ }
+ }
+
+ startTransaction.apply();
+ onFinish(null /* wct */, null /* wctCB */);
+ }
+
boolean isPendingTransition(IBinder transition) {
return getPendingTransition(transition) != null;
}
@@ -193,6 +232,10 @@ class SplitScreenTransitions {
return mPendingDismiss != null && mPendingDismiss.mTransition == transition;
}
+ boolean isPendingResize(IBinder transition) {
+ return mPendingResize != null && mPendingResize.mTransition == transition;
+ }
+
@Nullable
private TransitSession getPendingTransition(IBinder transition) {
if (isPendingEnter(transition)) {
@@ -201,11 +244,14 @@ class SplitScreenTransitions {
return mPendingRecent;
} else if (isPendingDismiss(transition)) {
return mPendingDismiss;
+ } else if (isPendingResize(transition)) {
+ return mPendingResize;
}
return null;
}
+
/** Starts a transition to enter split with a remote transition animator. */
IBinder startEnterTransition(
@WindowManager.TransitionType int transitType,
@@ -258,6 +304,21 @@ class SplitScreenTransitions {
exitReasonToString(reason), stageTypeToString(dismissTop));
}
+ IBinder startResizeTransition(WindowContainerTransaction wct,
+ Transitions.TransitionHandler handler,
+ @Nullable TransitionFinishedCallback finishCallback) {
+ IBinder transition = mTransitions.startTransition(TRANSIT_CHANGE, wct, handler);
+ setResizeTransition(transition, finishCallback);
+ return transition;
+ }
+
+ void setResizeTransition(@NonNull IBinder transition,
+ @Nullable TransitionFinishedCallback finishCallback) {
+ mPendingResize = new TransitSession(transition, null /* consumedCb */, finishCallback);
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " splitTransition "
+ + " deduced Resize split screen");
+ }
+
void setRecentTransition(@NonNull IBinder transition,
@Nullable RemoteTransition remoteTransition,
@Nullable TransitionFinishedCallback finishCallback) {
@@ -324,6 +385,9 @@ class SplitScreenTransitions {
mPendingRecent.onConsumed(aborted);
mPendingRecent = null;
mPendingRemoteHandler = null;
+ } else if (isPendingResize(transition)) {
+ mPendingResize.onConsumed(aborted);
+ mPendingResize = null;
}
}
@@ -340,6 +404,9 @@ class SplitScreenTransitions {
} else if (isPendingDismiss(mAnimatingTransition)) {
mPendingDismiss.onFinished(wct, mFinishTransaction);
mPendingDismiss = null;
+ } else if (isPendingResize(mAnimatingTransition)) {
+ mPendingResize.onFinished(wct, mFinishTransaction);
+ mPendingResize = null;
}
mPendingRemoteHandler = null;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java
index 2dc4a0441b06..1016e1bcd66f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java
@@ -23,6 +23,7 @@ import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED_
import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__APP_FINISHED;
import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__DEVICE_FOLDED;
import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__DRAG_DIVIDER;
+import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__FULLSCREEN_SHORTCUT;
import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__RETURN_HOME;
import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__ROOT_TASK_VANISHED;
import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__SCREEN_LOCKED;
@@ -38,6 +39,7 @@ import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_FINISHED;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DEVICE_FOLDED;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DRAG_DIVIDER;
+import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_FULLSCREEN_SHORTCUT;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_RETURN_HOME;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_ROOT_TASK_VANISHED;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_SCREEN_LOCKED;
@@ -180,6 +182,8 @@ public class SplitscreenEventLogger {
return SPLITSCREEN_UICHANGED__EXIT_REASON__SCREEN_LOCKED;
case EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP:
return SPLITSCREEN_UICHANGED__EXIT_REASON__SCREEN_LOCKED_SHOW_ON_TOP;
+ case EXIT_REASON_FULLSCREEN_SHORTCUT:
+ return SPLITSCREEN_UICHANGED__EXIT_REASON__FULLSCREEN_SHORTCUT;
case EXIT_REASON_UNKNOWN:
// Fall through
default:
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 4cb76230606f..da8dc8733ef5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -49,6 +49,7 @@ import static com.android.wm.shell.splitscreen.SplitScreenController.ENTER_REASO
import static com.android.wm.shell.splitscreen.SplitScreenController.ENTER_REASON_MULTI_INSTANCE;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_FINISHED;
+import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_FULLSCREEN_SHORTCUT;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_CHILD_TASK_ENTER_PIP;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DEVICE_FOLDED;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DRAG_DIVIDER;
@@ -150,7 +151,7 @@ import java.util.Optional;
*/
public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
DisplayController.OnDisplaysChangedListener, Transitions.TransitionHandler,
- ShellTaskOrganizer.TaskListener, ShellTaskOrganizer.FocusListener {
+ ShellTaskOrganizer.TaskListener {
private static final String TAG = StageCoordinator.class.getSimpleName();
@@ -186,8 +187,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
private final Rect mTempRect1 = new Rect();
private final Rect mTempRect2 = new Rect();
- private ActivityManager.RunningTaskInfo mFocusingTaskInfo;
-
/**
* A single-top root task which the split divider attached to.
*/
@@ -304,7 +303,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mDisplayController.addDisplayWindowListener(this);
mDisplayLayout = new DisplayLayout(displayController.getDisplayLayout(displayId));
transitions.addHandler(this);
- mTaskOrganizer.addFocusListener(this);
mSplitUnsupportedToast = Toast.makeText(mContext,
R.string.dock_non_resizeble_failed_to_dock_text, Toast.LENGTH_SHORT);
}
@@ -455,10 +453,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
/** Launches an activity into split by legacy transition. */
- void startIntentLegacy(PendingIntent intent, Intent fillInIntent,
- @SplitPosition int position, @Nullable Bundle options) {
- final WindowContainerTransaction evictWct = new WindowContainerTransaction();
- prepareEvictChildTasks(position, evictWct);
+ void startIntentLegacy(PendingIntent intent, Intent fillInIntent, @SplitPosition int position,
+ @Nullable Bundle options) {
+ final boolean isEnteringSplit = !isSplitActive();
LegacyTransitions.ILegacyTransition transition = new LegacyTransitions.ILegacyTransition() {
@Override
@@ -466,22 +463,28 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
IRemoteAnimationFinishedCallback finishedCallback,
SurfaceControl.Transaction t) {
- if (apps == null || apps.length == 0) {
- if (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0) {
- mMainExecutor.execute(() ->
- exitSplitScreen(mMainStage.getChildCount() == 0
- ? mSideStage : mMainStage, EXIT_REASON_UNKNOWN));
- mSplitUnsupportedToast.show();
+ boolean openingToSide = false;
+ if (apps != null) {
+ for (int i = 0; i < apps.length; ++i) {
+ if (apps[i].mode == MODE_OPENING
+ && mSideStage.containsTask(apps[i].taskId)) {
+ openingToSide = true;
+ break;
+ }
}
+ }
- // Do nothing when the animation was cancelled.
- t.apply();
- return;
+ if (isEnteringSplit && !openingToSide) {
+ mMainExecutor.execute(() -> exitSplitScreen(
+ mSideStage.getChildCount() == 0 ? mMainStage : mSideStage,
+ EXIT_REASON_UNKNOWN));
}
- for (int i = 0; i < apps.length; ++i) {
- if (apps[i].mode == MODE_OPENING) {
- t.show(apps[i].leash);
+ if (apps != null) {
+ for (int i = 0; i < apps.length; ++i) {
+ if (apps[i].mode == MODE_OPENING) {
+ t.show(apps[i].leash);
+ }
}
}
t.apply();
@@ -494,7 +497,12 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
}
- mSyncQueue.queue(evictWct);
+
+ if (!isEnteringSplit && openingToSide) {
+ final WindowContainerTransaction evictWct = new WindowContainerTransaction();
+ prepareEvictNonOpeningChildTasks(position, apps, evictWct);
+ mSyncQueue.queue(evictWct);
+ }
}
};
@@ -503,7 +511,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// If split still not active, apply windows bounds first to avoid surface reset to
// wrong pos by SurfaceAnimator from wms.
- if (!mMainStage.isActive() && mLogger.isEnterRequestedByDrag()) {
+ if (isEnteringSplit && mLogger.isEnterRequestedByDrag()) {
updateWindowBounds(mSplitLayout, wct);
}
@@ -1110,15 +1118,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
* Exits the split screen by finishing one of the tasks.
*/
protected void exitStage(@SplitPosition int stageToClose) {
- if (ENABLE_SHELL_TRANSITIONS) {
- StageTaskListener stageToTop = mSideStagePosition == stageToClose
- ? mMainStage
- : mSideStage;
- exitSplitScreen(stageToTop, EXIT_REASON_APP_FINISHED);
- } else {
- boolean toEnd = stageToClose == SPLIT_POSITION_BOTTOM_OR_RIGHT;
- mSplitLayout.flingDividerToDismiss(toEnd, EXIT_REASON_APP_FINISHED);
- }
+ mSplitLayout.flingDividerToDismiss(stageToClose == SPLIT_POSITION_BOTTOM_OR_RIGHT,
+ EXIT_REASON_APP_FINISHED);
}
/**
@@ -1152,6 +1153,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
case EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP:
// User has unlocked the device after folded
case EXIT_REASON_DEVICE_FOLDED:
+ // The device is folded
+ case EXIT_REASON_FULLSCREEN_SHORTCUT:
+ // User has used a keyboard shortcut to go back to fullscreen from split
return true;
default:
return false;
@@ -1615,15 +1619,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
&& ArrayUtils.contains(CONTROLLED_WINDOWING_MODES, taskInfo.getWindowingMode());
}
- ActivityManager.RunningTaskInfo getFocusingTaskInfo() {
- return mFocusingTaskInfo;
- }
-
- @Override
- public void onFocusTaskChanged(ActivityManager.RunningTaskInfo taskInfo) {
- mFocusingTaskInfo = taskInfo;
- }
-
@Override
public void onSnappedToDismiss(boolean bottomOrRight, int reason) {
final boolean mainStageToTop =
@@ -1674,15 +1669,29 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
public void onLayoutSizeChanged(SplitLayout layout) {
// Reset this flag every time onLayoutSizeChanged.
mShowDecorImmediately = false;
+
+ if (!ENABLE_SHELL_TRANSITIONS) {
+ // Only need screenshot for legacy case because shell transition should screenshot
+ // itself during transition.
+ final SurfaceControl.Transaction startT = mTransactionPool.acquire();
+ mMainStage.screenshotIfNeeded(startT);
+ mSideStage.screenshotIfNeeded(startT);
+ mTransactionPool.release(startT);
+ }
+
final WindowContainerTransaction wct = new WindowContainerTransaction();
updateWindowBounds(layout, wct);
sendOnBoundsChanged();
- mSyncQueue.queue(wct);
- mSyncQueue.runInSync(t -> {
- updateSurfaceBounds(layout, t, false /* applyResizingOffset */);
- mMainStage.onResized(t);
- mSideStage.onResized(t);
- });
+ if (ENABLE_SHELL_TRANSITIONS) {
+ mSplitTransitions.startResizeTransition(wct, this, null /* callback */);
+ } else {
+ mSyncQueue.queue(wct);
+ mSyncQueue.runInSync(t -> {
+ updateSurfaceBounds(layout, t, false /* applyResizingOffset */);
+ mMainStage.onResized(t);
+ mSideStage.onResized(t);
+ });
+ }
mLogger.logResize(mSplitLayout.getDividerPositionAsFraction());
}
@@ -2036,6 +2045,12 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
} else if (mSplitTransitions.isPendingDismiss(transition)) {
shouldAnimate = startPendingDismissAnimation(
mSplitTransitions.mPendingDismiss, info, startTransaction, finishTransaction);
+ } else if (mSplitTransitions.isPendingResize(transition)) {
+ mSplitTransitions.applyResizeTransition(transition, info, startTransaction,
+ finishTransaction, finishCallback, mMainStage.mRootTaskInfo.token,
+ mSideStage.mRootTaskInfo.token, mMainStage.getSplitDecorManager(),
+ mSideStage.getSplitDecorManager());
+ return true;
}
if (!shouldAnimate) return false;
@@ -2123,6 +2138,16 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
return true;
}
+ public void goToFullscreenFromSplit() {
+ boolean leftOrTop;
+ if (mSideStage.isFocused()) {
+ leftOrTop = (mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT);
+ } else {
+ leftOrTop = (mSideStagePosition == SPLIT_POSITION_BOTTOM_OR_RIGHT);
+ }
+ mSplitLayout.flingDividerToDismiss(!leftOrTop, EXIT_REASON_FULLSCREEN_SHORTCUT);
+ }
+
/** Synchronize split-screen state with transition and make appropriate preparations. */
public void prepareDismissAnimation(@StageType int toStage, @ExitReason int dismissReason,
@NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
index 358f712f76b5..8a52c8750ba6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
@@ -292,7 +292,13 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
void onResized(SurfaceControl.Transaction t) {
if (mSplitDecorManager != null) {
- mSplitDecorManager.onResized(t);
+ mSplitDecorManager.onResized(t, null);
+ }
+ }
+
+ void screenshotIfNeeded(SurfaceControl.Transaction t) {
+ if (mSplitDecorManager != null) {
+ mSplitDecorManager.screenshotIfNeeded(t);
}
}
@@ -304,6 +310,10 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
}
}
+ SplitDecorManager getSplitDecorManager() {
+ return mSplitDecorManager;
+ }
+
void addTask(ActivityManager.RunningTaskInfo task, WindowContainerTransaction wct) {
// Clear overridden bounds and windowing mode to make sure the child task can inherit
// windowing mode and bounds from split root.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java
index 8bba44049c88..20da8773f387 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java
@@ -50,13 +50,17 @@ public class SplashScreenExitAnimation implements Animator.AnimatorListener {
private final float mIconStartAlpha;
private final float mBrandingStartAlpha;
private final TransactionPool mTransactionPool;
+ // TODO(b/261167708): Clean enter animation code after moving Letterbox code to Shell
+ private final float mRoundedCornerRadius;
private Runnable mFinishCallback;
SplashScreenExitAnimation(Context context, SplashScreenView view, SurfaceControl leash,
- Rect frame, int mainWindowShiftLength, TransactionPool pool, Runnable handleFinish) {
+ Rect frame, int mainWindowShiftLength, TransactionPool pool, Runnable handleFinish,
+ float roundedCornerRadius) {
mSplashScreenView = view;
mFirstWindowSurface = leash;
+ mRoundedCornerRadius = roundedCornerRadius;
if (frame != null) {
mFirstWindowFrame.set(frame);
}
@@ -97,7 +101,7 @@ public class SplashScreenExitAnimation implements Animator.AnimatorListener {
SplashScreenExitAnimationUtils.startAnimations(mSplashScreenView, mFirstWindowSurface,
mMainWindowShiftLength, mTransactionPool, mFirstWindowFrame, mAnimationDuration,
mIconFadeOutDuration, mIconStartAlpha, mBrandingStartAlpha, mAppRevealDelay,
- mAppRevealDuration, this);
+ mAppRevealDuration, this, mRoundedCornerRadius);
}
private void reset() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java
index 3098e55ec78b..a7e4385b60c8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java
@@ -63,23 +63,39 @@ public class SplashScreenExitAnimationUtils {
/**
* Creates and starts the animator to fade out the icon, reveal the app, and shift up main
- * window.
- * @hide
+ * window with rounded corner radius.
*/
- public static void startAnimations(ViewGroup splashScreenView,
+ static void startAnimations(ViewGroup splashScreenView,
SurfaceControl firstWindowSurface, int mainWindowShiftLength,
TransactionPool transactionPool, Rect firstWindowFrame, int animationDuration,
int iconFadeOutDuration, float iconStartAlpha, float brandingStartAlpha,
- int appRevealDelay, int appRevealDuration, Animator.AnimatorListener animatorListener) {
+ int appRevealDelay, int appRevealDuration, Animator.AnimatorListener animatorListener,
+ float roundedCornerRadius) {
ValueAnimator animator =
createAnimator(splashScreenView, firstWindowSurface, mainWindowShiftLength,
transactionPool, firstWindowFrame, animationDuration, iconFadeOutDuration,
iconStartAlpha, brandingStartAlpha, appRevealDelay, appRevealDuration,
- animatorListener);
+ animatorListener, roundedCornerRadius);
animator.start();
}
/**
+ * Creates and starts the animator to fade out the icon, reveal the app, and shift up main
+ * window.
+ * @hide
+ */
+ public static void startAnimations(ViewGroup splashScreenView,
+ SurfaceControl firstWindowSurface, int mainWindowShiftLength,
+ TransactionPool transactionPool, Rect firstWindowFrame, int animationDuration,
+ int iconFadeOutDuration, float iconStartAlpha, float brandingStartAlpha,
+ int appRevealDelay, int appRevealDuration, Animator.AnimatorListener animatorListener) {
+ startAnimations(splashScreenView, firstWindowSurface, mainWindowShiftLength,
+ transactionPool, firstWindowFrame, animationDuration, iconFadeOutDuration,
+ iconStartAlpha, brandingStartAlpha, appRevealDelay, appRevealDuration,
+ animatorListener, 0f /* roundedCornerRadius */);
+ }
+
+ /**
* Creates the animator to fade out the icon, reveal the app, and shift up main window.
* @hide
*/
@@ -87,7 +103,8 @@ public class SplashScreenExitAnimationUtils {
SurfaceControl firstWindowSurface, int mMainWindowShiftLength,
TransactionPool transactionPool, Rect firstWindowFrame, int animationDuration,
int iconFadeOutDuration, float iconStartAlpha, float brandingStartAlpha,
- int appRevealDelay, int appRevealDuration, Animator.AnimatorListener animatorListener) {
+ int appRevealDelay, int appRevealDuration, Animator.AnimatorListener animatorListener,
+ float roundedCornerRadius) {
// reveal app
final float transparentRatio = 0.8f;
final int globalHeight = splashScreenView.getHeight();
@@ -124,7 +141,7 @@ public class SplashScreenExitAnimationUtils {
shiftUpAnimation = new ShiftUpAnimation(0, -mMainWindowShiftLength, occludeHoleView,
firstWindowSurface, splashScreenView, transactionPool, firstWindowFrame,
- mMainWindowShiftLength);
+ mMainWindowShiftLength, roundedCornerRadius);
}
ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
@@ -289,8 +306,8 @@ public class SplashScreenExitAnimationUtils {
public ShiftUpAnimation(float fromYDelta, float toYDelta, View occludeHoleView,
SurfaceControl firstWindowSurface, ViewGroup splashScreenView,
TransactionPool transactionPool, Rect firstWindowFrame,
- int mainWindowShiftLength) {
- mFromYDelta = fromYDelta;
+ int mainWindowShiftLength, float roundedCornerRadius) {
+ mFromYDelta = fromYDelta - roundedCornerRadius;
mToYDelta = toYDelta;
mOccludeHoleView = occludeHoleView;
mApplier = new SyncRtSurfaceTransactionApplier(occludeHoleView);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
index 6ce981e25f5e..ebb957b2201b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
@@ -16,8 +16,10 @@
package com.android.wm.shell.startingsurface;
+import static android.content.Context.CONTEXT_RESTRICTED;
import static android.os.Process.THREAD_PRIORITY_TOP_APP_BOOST;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
+import static android.view.Display.DEFAULT_DISPLAY;
import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN;
import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SOLID_COLOR_SPLASH_SCREEN;
import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SPLASH_SCREEN;
@@ -29,6 +31,7 @@ import android.annotation.ColorInt;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.ActivityManager;
import android.app.ActivityThread;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -48,9 +51,11 @@ import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
+import android.hardware.display.DisplayManager;
import android.net.Uri;
import android.os.Handler;
import android.os.HandlerThread;
+import android.os.IBinder;
import android.os.SystemClock;
import android.os.Trace;
import android.os.UserHandle;
@@ -58,7 +63,9 @@ import android.util.ArrayMap;
import android.util.DisplayMetrics;
import android.util.Slog;
import android.view.ContextThemeWrapper;
+import android.view.Display;
import android.view.SurfaceControl;
+import android.view.WindowManager;
import android.window.SplashScreenView;
import android.window.StartingWindowInfo;
import android.window.StartingWindowInfo.StartingWindowType;
@@ -134,6 +141,144 @@ public class SplashscreenContentDrawer {
}
/**
+ * Help method to create a layout parameters for a window.
+ */
+ static Context createContext(Context initContext, StartingWindowInfo windowInfo,
+ int theme, @StartingWindowInfo.StartingWindowType int suggestType,
+ DisplayManager displayManager) {
+ final ActivityManager.RunningTaskInfo taskInfo = windowInfo.taskInfo;
+ final ActivityInfo activityInfo = windowInfo.targetActivityInfo != null
+ ? windowInfo.targetActivityInfo
+ : taskInfo.topActivityInfo;
+ if (activityInfo == null || activityInfo.packageName == null) {
+ return null;
+ }
+
+ final int displayId = taskInfo.displayId;
+ final int taskId = taskInfo.taskId;
+
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
+ "addSplashScreen for package: %s with theme: %s for task: %d, suggestType: %d",
+ activityInfo.packageName, Integer.toHexString(theme), taskId, suggestType);
+ final Display display = displayManager.getDisplay(displayId);
+ if (display == null) {
+ // Can't show splash screen on requested display, so skip showing at all.
+ return null;
+ }
+ Context context = displayId == DEFAULT_DISPLAY
+ ? initContext : initContext.createDisplayContext(display);
+ if (context == null) {
+ return null;
+ }
+ if (theme != context.getThemeResId()) {
+ try {
+ context = context.createPackageContextAsUser(activityInfo.packageName,
+ CONTEXT_RESTRICTED, UserHandle.of(taskInfo.userId));
+ context.setTheme(theme);
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.w(TAG, "Failed creating package context with package name "
+ + activityInfo.packageName + " for user " + taskInfo.userId, e);
+ return null;
+ }
+ }
+
+ final Configuration taskConfig = taskInfo.getConfiguration();
+ if (taskConfig.diffPublicOnly(context.getResources().getConfiguration()) != 0) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
+ "addSplashScreen: creating context based on task Configuration %s",
+ taskConfig);
+ final Context overrideContext = context.createConfigurationContext(taskConfig);
+ overrideContext.setTheme(theme);
+ final TypedArray typedArray = overrideContext.obtainStyledAttributes(
+ com.android.internal.R.styleable.Window);
+ final int resId = typedArray.getResourceId(R.styleable.Window_windowBackground, 0);
+ try {
+ if (resId != 0 && overrideContext.getDrawable(resId) != null) {
+ // We want to use the windowBackground for the override context if it is
+ // available, otherwise we use the default one to make sure a themed starting
+ // window is displayed for the app.
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
+ "addSplashScreen: apply overrideConfig %s",
+ taskConfig);
+ context = overrideContext;
+ }
+ } catch (Resources.NotFoundException e) {
+ Slog.w(TAG, "failed creating starting window for overrideConfig at taskId: "
+ + taskId, e);
+ return null;
+ }
+ typedArray.recycle();
+ }
+ return context;
+ }
+
+ /**
+ * Creates the window layout parameters for splashscreen window.
+ */
+ static WindowManager.LayoutParams createLayoutParameters(Context context,
+ StartingWindowInfo windowInfo,
+ @StartingWindowInfo.StartingWindowType int suggestType,
+ CharSequence title, int pixelFormat, IBinder appToken) {
+ final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
+ WindowManager.LayoutParams.TYPE_APPLICATION_STARTING);
+ params.setFitInsetsSides(0);
+ params.setFitInsetsTypes(0);
+ params.format = pixelFormat;
+ int windowFlags = WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
+ | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+ | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
+ final TypedArray a = context.obtainStyledAttributes(R.styleable.Window);
+ if (a.getBoolean(R.styleable.Window_windowShowWallpaper, false)) {
+ windowFlags |= WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
+ }
+ if (suggestType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN) {
+ if (a.getBoolean(R.styleable.Window_windowDrawsSystemBarBackgrounds, false)) {
+ windowFlags |= WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+ }
+ } else {
+ windowFlags |= WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+ }
+ params.layoutInDisplayCutoutMode = a.getInt(
+ R.styleable.Window_windowLayoutInDisplayCutoutMode,
+ params.layoutInDisplayCutoutMode);
+ params.windowAnimations = a.getResourceId(R.styleable.Window_windowAnimationStyle, 0);
+ a.recycle();
+
+ final ActivityManager.RunningTaskInfo taskInfo = windowInfo.taskInfo;
+ final ActivityInfo activityInfo = windowInfo.targetActivityInfo != null
+ ? windowInfo.targetActivityInfo
+ : taskInfo.topActivityInfo;
+ final int displayId = taskInfo.displayId;
+ // Assumes it's safe to show starting windows of launched apps while
+ // the keyguard is being hidden. This is okay because starting windows never show
+ // secret information.
+ // TODO(b/113840485): Occluded may not only happen on default display
+ if (displayId == DEFAULT_DISPLAY && windowInfo.isKeyguardOccluded) {
+ windowFlags |= WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
+ }
+
+ // Force the window flags: this is a fake window, so it is not really
+ // touchable or focusable by the user. We also add in the ALT_FOCUSABLE_IM
+ // flag because we do know that the next window will take input
+ // focus, so we want to get the IME window up on top of us right away.
+ // Touches will only pass through to the host activity window and will be blocked from
+ // passing to any other windows.
+ windowFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
+ | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
+ params.flags = windowFlags;
+ params.token = appToken;
+ params.packageName = activityInfo.packageName;
+ params.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
+
+ if (!context.getResources().getCompatibilityInfo().supportsScreen()) {
+ params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
+ }
+
+ params.setTitle("Splash Screen " + title);
+ return params;
+ }
+ /**
* Create a SplashScreenView object.
*
* In order to speed up the splash screen view to show on first frame, preparing the
@@ -248,6 +393,26 @@ public class SplashscreenContentDrawer {
return null;
}
+ /**
+ * Creates a SplashScreenView without read animatable icon and branding image.
+ */
+ SplashScreenView makeSimpleSplashScreenContentView(Context context,
+ StartingWindowInfo info, int themeBGColor) {
+ updateDensity();
+ mTmpAttrs.reset();
+ final ActivityInfo ai = info.targetActivityInfo != null
+ ? info.targetActivityInfo
+ : info.taskInfo.topActivityInfo;
+
+ final SplashViewBuilder builder = new SplashViewBuilder(context, ai);
+ final SplashScreenView view = builder
+ .setWindowBGColor(themeBGColor)
+ .chooseStyle(STARTING_WINDOW_TYPE_SPLASH_SCREEN)
+ .build();
+ view.setNotCopyable();
+ return view;
+ }
+
private SplashScreenView makeSplashScreenContentView(Context context, StartingWindowInfo info,
@StartingWindowType int suggestType, Consumer<Runnable> uiThreadInitConsumer) {
updateDensity();
@@ -263,7 +428,8 @@ public class SplashscreenContentDrawer {
final int themeBGColor = legacyDrawable != null
? getBGColorFromCache(ai, () -> estimateWindowBGColor(legacyDrawable))
: getBGColorFromCache(ai, () -> peekWindowBGColor(context, mTmpAttrs));
- return new StartingWindowViewBuilder(context, ai)
+
+ return new SplashViewBuilder(context, ai)
.setWindowBGColor(themeBGColor)
.overlayDrawable(legacyDrawable)
.chooseStyle(suggestType)
@@ -322,6 +488,14 @@ public class SplashscreenContentDrawer {
private Drawable mSplashScreenIcon = null;
private Drawable mBrandingImage = null;
private int mIconBgColor = Color.TRANSPARENT;
+
+ void reset() {
+ mWindowBgResId = 0;
+ mWindowBgColor = Color.TRANSPARENT;
+ mSplashScreenIcon = null;
+ mBrandingImage = null;
+ mIconBgColor = Color.TRANSPARENT;
+ }
}
/**
@@ -351,7 +525,7 @@ public class SplashscreenContentDrawer {
return appReadyDuration;
}
- private class StartingWindowViewBuilder {
+ private class SplashViewBuilder {
private final Context mContext;
private final ActivityInfo mActivityInfo;
@@ -364,27 +538,28 @@ public class SplashscreenContentDrawer {
/** @see #setAllowHandleSolidColor(boolean) **/
private boolean mAllowHandleSolidColor;
- StartingWindowViewBuilder(@NonNull Context context, @NonNull ActivityInfo aInfo) {
+ SplashViewBuilder(@NonNull Context context, @NonNull ActivityInfo aInfo) {
mContext = context;
mActivityInfo = aInfo;
}
- StartingWindowViewBuilder setWindowBGColor(@ColorInt int background) {
+ SplashViewBuilder setWindowBGColor(@ColorInt int background) {
mThemeColor = background;
return this;
}
- StartingWindowViewBuilder overlayDrawable(Drawable overlay) {
+ SplashViewBuilder overlayDrawable(Drawable overlay) {
mOverlayDrawable = overlay;
return this;
}
- StartingWindowViewBuilder chooseStyle(int suggestType) {
+ SplashViewBuilder chooseStyle(int suggestType) {
mSuggestType = suggestType;
return this;
}
- StartingWindowViewBuilder setUiThreadInitConsumer(Consumer<Runnable> uiThreadInitTask) {
+ // Set up the UI thread for the View.
+ SplashViewBuilder setUiThreadInitConsumer(Consumer<Runnable> uiThreadInitTask) {
mUiThreadInitTask = uiThreadInitTask;
return this;
}
@@ -395,7 +570,7 @@ public class SplashscreenContentDrawer {
* android.window.SplashScreen.OnExitAnimationListener#onSplashScreenExit(SplashScreenView)}
* callback, effectively copying the {@link SplashScreenView} into the client process.
*/
- StartingWindowViewBuilder setAllowHandleSolidColor(boolean allowHandleSolidColor) {
+ SplashViewBuilder setAllowHandleSolidColor(boolean allowHandleSolidColor) {
mAllowHandleSolidColor = allowHandleSolidColor;
return this;
}
@@ -993,10 +1168,11 @@ public class SplashscreenContentDrawer {
* Create and play the default exit animation for splash screen view.
*/
void applyExitAnimation(SplashScreenView view, SurfaceControl leash,
- Rect frame, Runnable finishCallback, long createTime) {
+ Rect frame, Runnable finishCallback, long createTime, float roundedCornerRadius) {
final Runnable playAnimation = () -> {
final SplashScreenExitAnimation animation = new SplashScreenExitAnimation(mContext,
- view, leash, frame, mMainWindowShiftLength, mTransactionPool, finishCallback);
+ view, leash, frame, mMainWindowShiftLength, mTransactionPool, finishCallback,
+ roundedCornerRadius);
animation.startAnimations();
};
if (view.getIconView() == null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java
index 7f6bfd23f72b..e419462012e3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java
@@ -62,7 +62,7 @@ public class SplashscreenIconDrawableFactory {
*/
static Drawable[] makeIconDrawable(@ColorInt int backgroundColor, @ColorInt int themeColor,
@NonNull Drawable foregroundDrawable, int srcIconSize, int iconSize,
- boolean loadInDetail, Handler splashscreenWorkerHandler) {
+ boolean loadInDetail, Handler preDrawHandler) {
Drawable foreground;
Drawable background = null;
boolean drawBackground =
@@ -74,13 +74,13 @@ public class SplashscreenIconDrawableFactory {
// If the icon is Adaptive, we already use the icon background.
drawBackground = false;
foreground = new ImmobileIconDrawable(foregroundDrawable,
- srcIconSize, iconSize, loadInDetail, splashscreenWorkerHandler);
+ srcIconSize, iconSize, loadInDetail, preDrawHandler);
} else {
// Adaptive icon don't handle transparency so we draw the background of the adaptive
// icon with the same color as the window background color instead of using two layers
foreground = new ImmobileIconDrawable(
new AdaptiveForegroundDrawable(foregroundDrawable),
- srcIconSize, iconSize, loadInDetail, splashscreenWorkerHandler);
+ srcIconSize, iconSize, loadInDetail, preDrawHandler);
}
if (drawBackground) {
@@ -91,9 +91,9 @@ public class SplashscreenIconDrawableFactory {
}
static Drawable[] makeLegacyIconDrawable(@NonNull Drawable iconDrawable, int srcIconSize,
- int iconSize, boolean loadInDetail, Handler splashscreenWorkerHandler) {
+ int iconSize, boolean loadInDetail, Handler preDrawHandler) {
return new Drawable[]{new ImmobileIconDrawable(iconDrawable, srcIconSize, iconSize,
- loadInDetail, splashscreenWorkerHandler)};
+ loadInDetail, preDrawHandler)};
}
/**
@@ -107,14 +107,14 @@ public class SplashscreenIconDrawableFactory {
private Bitmap mIconBitmap;
ImmobileIconDrawable(Drawable drawable, int srcIconSize, int iconSize, boolean loadInDetail,
- Handler splashscreenWorkerHandler) {
+ Handler preDrawHandler) {
// This icon has lower density, don't scale it.
if (loadInDetail) {
- splashscreenWorkerHandler.post(() -> preDrawIcon(drawable, iconSize));
+ preDrawHandler.post(() -> preDrawIcon(drawable, iconSize));
} else {
final float scale = (float) iconSize / srcIconSize;
mMatrix.setScale(scale, scale);
- splashscreenWorkerHandler.post(() -> preDrawIcon(drawable, srcIconSize));
+ preDrawHandler.post(() -> preDrawIcon(drawable, srcIconSize));
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
index ff6f2b03c02c..4f07bfeacce5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
@@ -16,7 +16,6 @@
package com.android.wm.shell.startingsurface;
-import static android.content.Context.CONTEXT_RESTRICTED;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.view.Choreographer.CALLBACK_INSETS_ANIMATION;
import static android.view.Display.DEFAULT_DISPLAY;
@@ -32,8 +31,6 @@ import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
-import android.content.res.Configuration;
-import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.PixelFormat;
@@ -198,118 +195,21 @@ public class StartingSurfaceDrawer {
if (activityInfo == null || activityInfo.packageName == null) {
return;
}
-
- final int displayId = taskInfo.displayId;
- final int taskId = taskInfo.taskId;
-
// replace with the default theme if the application didn't set
final int theme = getSplashScreenTheme(windowInfo.splashScreenThemeResId, activityInfo);
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
- "addSplashScreen for package: %s with theme: %s for task: %d, suggestType: %d",
- activityInfo.packageName, Integer.toHexString(theme), taskId, suggestType);
- final Display display = getDisplay(displayId);
- if (display == null) {
- // Can't show splash screen on requested display, so skip showing at all.
- return;
- }
- Context context = displayId == DEFAULT_DISPLAY
- ? mContext : mContext.createDisplayContext(display);
+ final Context context = SplashscreenContentDrawer.createContext(mContext, windowInfo, theme,
+ suggestType, mDisplayManager);
if (context == null) {
return;
}
- if (theme != context.getThemeResId()) {
- try {
- context = context.createPackageContextAsUser(activityInfo.packageName,
- CONTEXT_RESTRICTED, UserHandle.of(taskInfo.userId));
- context.setTheme(theme);
- } catch (PackageManager.NameNotFoundException e) {
- Slog.w(TAG, "Failed creating package context with package name "
- + activityInfo.packageName + " for user " + taskInfo.userId, e);
- return;
- }
- }
+ final WindowManager.LayoutParams params = SplashscreenContentDrawer.createLayoutParameters(
+ context, windowInfo, suggestType, activityInfo.packageName,
+ suggestType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN
+ ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT, appToken);
- final Configuration taskConfig = taskInfo.getConfiguration();
- if (taskConfig.diffPublicOnly(context.getResources().getConfiguration()) != 0) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
- "addSplashScreen: creating context based on task Configuration %s",
- taskConfig);
- final Context overrideContext = context.createConfigurationContext(taskConfig);
- overrideContext.setTheme(theme);
- final TypedArray typedArray = overrideContext.obtainStyledAttributes(
- com.android.internal.R.styleable.Window);
- final int resId = typedArray.getResourceId(R.styleable.Window_windowBackground, 0);
- try {
- if (resId != 0 && overrideContext.getDrawable(resId) != null) {
- // We want to use the windowBackground for the override context if it is
- // available, otherwise we use the default one to make sure a themed starting
- // window is displayed for the app.
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
- "addSplashScreen: apply overrideConfig %s",
- taskConfig);
- context = overrideContext;
- }
- } catch (Resources.NotFoundException e) {
- Slog.w(TAG, "failed creating starting window for overrideConfig at taskId: "
- + taskId, e);
- return;
- }
- typedArray.recycle();
- }
-
- final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
- WindowManager.LayoutParams.TYPE_APPLICATION_STARTING);
- params.setFitInsetsSides(0);
- params.setFitInsetsTypes(0);
- params.format = suggestType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN
- ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT;
- int windowFlags = WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
- | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
- | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
- final TypedArray a = context.obtainStyledAttributes(R.styleable.Window);
- if (a.getBoolean(R.styleable.Window_windowShowWallpaper, false)) {
- windowFlags |= WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
- }
- if (suggestType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN) {
- if (a.getBoolean(R.styleable.Window_windowDrawsSystemBarBackgrounds, false)) {
- windowFlags |= WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
- }
- } else {
- windowFlags |= WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
- }
- params.layoutInDisplayCutoutMode = a.getInt(
- R.styleable.Window_windowLayoutInDisplayCutoutMode,
- params.layoutInDisplayCutoutMode);
- params.windowAnimations = a.getResourceId(R.styleable.Window_windowAnimationStyle, 0);
- a.recycle();
-
- // Assumes it's safe to show starting windows of launched apps while
- // the keyguard is being hidden. This is okay because starting windows never show
- // secret information.
- // TODO(b/113840485): Occluded may not only happen on default display
- if (displayId == DEFAULT_DISPLAY && windowInfo.isKeyguardOccluded) {
- windowFlags |= WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
- }
-
- // Force the window flags: this is a fake window, so it is not really
- // touchable or focusable by the user. We also add in the ALT_FOCUSABLE_IM
- // flag because we do know that the next window will take input
- // focus, so we want to get the IME window up on top of us right away.
- // Touches will only pass through to the host activity window and will be blocked from
- // passing to any other windows.
- windowFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
- | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
- | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
- params.flags = windowFlags;
- params.token = appToken;
- params.packageName = activityInfo.packageName;
- params.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
-
- if (!context.getResources().getCompatibilityInfo().supportsScreen()) {
- params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
- }
-
- params.setTitle("Splash Screen " + activityInfo.packageName);
+ final int displayId = taskInfo.displayId;
+ final int taskId = taskInfo.taskId;
+ final Display display = getDisplay(displayId);
// TODO(b/173975965) tracking performance
// Prepare the splash screen content view on splash screen worker thread in parallel, so the
@@ -646,7 +546,7 @@ public class StartingSurfaceDrawer {
mSplashscreenContentDrawer.applyExitAnimation(record.mContentView,
removalInfo.windowAnimationLeash, removalInfo.mainFrame,
() -> removeWindowInner(record.mDecorView, true),
- record.mCreateTime);
+ record.mCreateTime, removalInfo.roundedCornerRadius);
} else {
// the SplashScreenView has been copied to client, hide the view to skip
// default exit animation
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
index 3929e835cdaa..9d6711f42efe 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
@@ -18,50 +18,16 @@ package com.android.wm.shell.startingsurface;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.graphics.Color.WHITE;
-import static android.graphics.Color.alpha;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
-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;
-import static android.view.WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE;
-import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
-import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
-import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
-import static android.view.WindowManager.LayoutParams.FLAG_SCALED;
-import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
-import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY;
-import static android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
-import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION;
-import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
-import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_USE_BLAST;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
-import static com.android.internal.policy.DecorView.NAVIGATION_BAR_COLOR_VIEW_ATTRIBUTES;
-import static com.android.internal.policy.DecorView.STATUS_BAR_COLOR_VIEW_ATTRIBUTES;
-import static com.android.internal.policy.DecorView.getNavigationBarRect;
-
import android.annotation.BinderThread;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityManager.TaskDescription;
-import android.app.ActivityThread;
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.GraphicBuffer;
-import android.graphics.Matrix;
import android.graphics.Paint;
-import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.Rect;
-import android.graphics.RectF;
-import android.hardware.HardwareBuffer;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
@@ -73,20 +39,14 @@ import android.view.InputChannel;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
import android.view.SurfaceControl;
-import android.view.SurfaceSession;
import android.view.View;
-import android.view.ViewGroup;
-import android.view.WindowInsets;
-import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import android.window.ClientWindowFrames;
+import android.window.SnapshotDrawerUtils;
import android.window.StartingWindowInfo;
import android.window.TaskSnapshot;
-import com.android.internal.R;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.policy.DecorView;
import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.view.BaseIWindow;
import com.android.wm.shell.common.ShellExecutor;
@@ -100,27 +60,8 @@ import java.lang.ref.WeakReference;
* @hide
*/
public class TaskSnapshotWindow {
- /**
- * When creating the starting window, we use the exact same layout flags such that we end up
- * with a window with the exact same dimensions etc. However, these flags are not used in layout
- * and might cause other side effects so we exclude them.
- */
- static final int FLAG_INHERIT_EXCLUDES = FLAG_NOT_FOCUSABLE
- | FLAG_NOT_TOUCHABLE
- | FLAG_NOT_TOUCH_MODAL
- | FLAG_ALT_FOCUSABLE_IM
- | FLAG_NOT_FOCUSABLE
- | FLAG_HARDWARE_ACCELERATED
- | FLAG_IGNORE_CHEEK_PRESSES
- | FLAG_LOCAL_FOCUS_MODE
- | FLAG_SLIPPERY
- | FLAG_WATCH_OUTSIDE_TOUCH
- | FLAG_SPLIT_TOUCH
- | FLAG_SCALED
- | FLAG_SECURE;
-
private static final String TAG = StartingWindowController.TAG;
- private static final String TITLE_FORMAT = "SnapshotStartingWindow for taskId=%s";
+ private static final String TITLE_FORMAT = "SnapshotStartingWindow for taskId=";
private static final long DELAY_REMOVAL_TIME_GENERAL = 100;
/**
@@ -133,25 +74,12 @@ public class TaskSnapshotWindow {
private final Window mWindow;
private final Runnable mClearWindowHandler;
private final ShellExecutor mSplashScreenExecutor;
- private final SurfaceControl mSurfaceControl;
private final IWindowSession mSession;
- private final Rect mTaskBounds;
- private final Rect mFrame = new Rect();
- private final Rect mSystemBarInsets = new Rect();
- private TaskSnapshot mSnapshot;
- private final RectF mTmpSnapshotSize = new RectF();
- private final RectF mTmpDstFrame = new RectF();
- private final CharSequence mTitle;
private boolean mHasDrawn;
- private boolean mSizeMismatch;
private final Paint mBackgroundPaint = new Paint();
private final int mActivityType;
- private final int mStatusBarColor;
- private final SystemBarBackgroundPainter mSystemBarBackgroundPainter;
private final int mOrientationOnCreation;
- private final SurfaceControl.Transaction mTransaction;
- private final Matrix mSnapshotMatrix = new Matrix();
- private final float[] mTmpFloat9 = new float[9];
+
private final Runnable mScheduledRunnable = this::removeImmediately;
private final boolean mHasImeSurface;
@@ -163,42 +91,15 @@ public class TaskSnapshotWindow {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
"create taskSnapshot surface for task: %d", taskId);
- final WindowManager.LayoutParams attrs = info.topOpaqueWindowLayoutParams;
- final WindowManager.LayoutParams mainWindowParams = info.mainWindowLayoutParams;
final InsetsState topWindowInsetsState = info.topOpaqueWindowInsetsState;
- if (attrs == null || mainWindowParams == null || topWindowInsetsState == null) {
- Slog.w(TAG, "unable to create taskSnapshot surface for task: " + taskId);
+
+ final WindowManager.LayoutParams layoutParams = SnapshotDrawerUtils.createLayoutParameters(
+ info, TITLE_FORMAT + taskId, TYPE_APPLICATION_STARTING,
+ snapshot.getHardwareBuffer().getFormat(), appToken);
+ if (layoutParams == null) {
+ Slog.e(TAG, "TaskSnapshotWindow no layoutParams");
return null;
}
- final WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
-
- final int appearance = attrs.insetsFlags.appearance;
- final int windowFlags = attrs.flags;
- final int windowPrivateFlags = attrs.privateFlags;
-
- layoutParams.packageName = mainWindowParams.packageName;
- layoutParams.windowAnimations = mainWindowParams.windowAnimations;
- layoutParams.dimAmount = mainWindowParams.dimAmount;
- layoutParams.type = TYPE_APPLICATION_STARTING;
- layoutParams.format = snapshot.getHardwareBuffer().getFormat();
- layoutParams.flags = (windowFlags & ~FLAG_INHERIT_EXCLUDES)
- | FLAG_NOT_FOCUSABLE
- | FLAG_NOT_TOUCHABLE;
- // Setting as trusted overlay to let touches pass through. This is safe because this
- // window is controlled by the system.
- layoutParams.privateFlags = (windowPrivateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS)
- | PRIVATE_FLAG_TRUSTED_OVERLAY | PRIVATE_FLAG_USE_BLAST;
- layoutParams.token = appToken;
- layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
- layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT;
- layoutParams.insetsFlags.appearance = appearance;
- layoutParams.insetsFlags.behavior = attrs.insetsFlags.behavior;
- layoutParams.layoutInDisplayCutoutMode = attrs.layoutInDisplayCutoutMode;
- layoutParams.setFitInsetsTypes(attrs.getFitInsetsTypes());
- layoutParams.setFitInsetsSides(attrs.getFitInsetsSides());
- layoutParams.setFitInsetsIgnoringVisibility(attrs.isFitInsetsIgnoringVisibility());
-
- layoutParams.setTitle(String.format(TITLE_FORMAT, taskId));
final Point taskSize = snapshot.getTaskSize();
final Rect taskBounds = new Rect(0, 0, taskSize.x, taskSize.y);
@@ -222,9 +123,8 @@ public class TaskSnapshotWindow {
}
final TaskSnapshotWindow snapshotSurface = new TaskSnapshotWindow(
- surfaceControl, snapshot, layoutParams.getTitle(), taskDescription, appearance,
- windowFlags, windowPrivateFlags, taskBounds, orientation, activityType,
- info.requestedVisibleTypes, clearWindowHandler, splashScreenExecutor);
+ snapshot, taskDescription, orientation, activityType,
+ clearWindowHandler, splashScreenExecutor);
final Window window = snapshotSurface.mWindow;
final InsetsState tmpInsetsState = new InsetsState();
@@ -255,33 +155,25 @@ public class TaskSnapshotWindow {
snapshotSurface.clearWindowSynced();
}
- final Rect systemBarInsets = getSystemBarInsets(tmpFrames.frame, topWindowInsetsState);
- snapshotSurface.setFrames(tmpFrames.frame, systemBarInsets);
- snapshotSurface.drawSnapshot();
+ SnapshotDrawerUtils.drawSnapshotOnSurface(info, layoutParams, surfaceControl, snapshot,
+ taskBounds, tmpFrames.frame, topWindowInsetsState, true /* releaseAfterDraw */);
+ snapshotSurface.mHasDrawn = true;
+ snapshotSurface.reportDrawn();
+
return snapshotSurface;
}
- public TaskSnapshotWindow(SurfaceControl surfaceControl,
- TaskSnapshot snapshot, CharSequence title, TaskDescription taskDescription,
- int appearance, int windowFlags, int windowPrivateFlags, Rect taskBounds,
- int currentOrientation, int activityType, @InsetsType int requestedVisibleTypes,
- Runnable clearWindowHandler, ShellExecutor splashScreenExecutor) {
+ public TaskSnapshotWindow(TaskSnapshot snapshot, TaskDescription taskDescription,
+ int currentOrientation, int activityType, Runnable clearWindowHandler,
+ ShellExecutor splashScreenExecutor) {
mSplashScreenExecutor = splashScreenExecutor;
mSession = WindowManagerGlobal.getWindowSession();
mWindow = new Window();
mWindow.setSession(mSession);
- mSurfaceControl = surfaceControl;
- mSnapshot = snapshot;
- mTitle = title;
int backgroundColor = taskDescription.getBackgroundColor();
mBackgroundPaint.setColor(backgroundColor != 0 ? backgroundColor : WHITE);
- mTaskBounds = taskBounds;
- mSystemBarBackgroundPainter = new SystemBarBackgroundPainter(windowFlags,
- windowPrivateFlags, appearance, taskDescription, 1f, requestedVisibleTypes);
- mStatusBarColor = taskDescription.getStatusBarColor();
mOrientationOnCreation = currentOrientation;
mActivityType = activityType;
- mTransaction = new SurfaceControl.Transaction();
mClearWindowHandler = clearWindowHandler;
mHasImeSurface = snapshot.hasImeSurface();
}
@@ -294,23 +186,6 @@ public class TaskSnapshotWindow {
return mHasImeSurface;
}
- /**
- * Ask system bar background painter to draw status bar background.
- * @hide
- */
- public void drawStatusBarBackground(Canvas c, @Nullable Rect alreadyDrawnFrame) {
- mSystemBarBackgroundPainter.drawStatusBarBackground(c, alreadyDrawnFrame,
- mSystemBarBackgroundPainter.getStatusBarColorViewHeight());
- }
-
- /**
- * Ask system bar background painter to draw navigation bar background.
- * @hide
- */
- public void drawNavigationBarBackground(Canvas c) {
- mSystemBarBackgroundPainter.drawNavigationBarBackground(c);
- }
-
void scheduleRemove(boolean deferRemoveForIme) {
// Show the latest content as soon as possible for unlocking to home.
if (mActivityType == ACTIVITY_TYPE_HOME) {
@@ -338,178 +213,6 @@ public class TaskSnapshotWindow {
}
/**
- * Set frame size.
- * @hide
- */
- public void setFrames(Rect frame, Rect systemBarInsets) {
- mFrame.set(frame);
- mSystemBarInsets.set(systemBarInsets);
- final HardwareBuffer snapshot = mSnapshot.getHardwareBuffer();
- mSizeMismatch = (mFrame.width() != snapshot.getWidth()
- || mFrame.height() != snapshot.getHeight());
- mSystemBarBackgroundPainter.setInsets(systemBarInsets);
- }
-
- static Rect getSystemBarInsets(Rect frame, InsetsState state) {
- return state.calculateInsets(frame, WindowInsets.Type.systemBars(),
- false /* ignoreVisibility */).toRect();
- }
-
- private void drawSnapshot() {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
- "Drawing snapshot surface sizeMismatch=%b", mSizeMismatch);
- if (mSizeMismatch) {
- // The dimensions of the buffer and the window don't match, so attaching the buffer
- // will fail. Better create a child window with the exact dimensions and fill the parent
- // window with the background color!
- drawSizeMismatchSnapshot();
- } else {
- drawSizeMatchSnapshot();
- }
- mHasDrawn = true;
- reportDrawn();
-
- // In case window manager leaks us, make sure we don't retain the snapshot.
- if (mSnapshot.getHardwareBuffer() != null) {
- mSnapshot.getHardwareBuffer().close();
- }
- mSnapshot = null;
- mSurfaceControl.release();
- }
-
- private void drawSizeMatchSnapshot() {
- mTransaction.setBuffer(mSurfaceControl, mSnapshot.getHardwareBuffer())
- .setColorSpace(mSurfaceControl, mSnapshot.getColorSpace())
- .apply();
- }
-
- private void drawSizeMismatchSnapshot() {
- final HardwareBuffer buffer = mSnapshot.getHardwareBuffer();
- final SurfaceSession session = new SurfaceSession();
-
- // We consider nearly matched dimensions as there can be rounding errors and the user won't
- // notice very minute differences from scaling one dimension more than the other
- final boolean aspectRatioMismatch = Math.abs(
- ((float) buffer.getWidth() / buffer.getHeight())
- - ((float) mFrame.width() / mFrame.height())) > 0.01f;
-
- // Keep a reference to it such that it doesn't get destroyed when finalized.
- SurfaceControl childSurfaceControl = new SurfaceControl.Builder(session)
- .setName(mTitle + " - task-snapshot-surface")
- .setBLASTLayer()
- .setFormat(buffer.getFormat())
- .setParent(mSurfaceControl)
- .setCallsite("TaskSnapshotWindow.drawSizeMismatchSnapshot")
- .build();
-
- final Rect frame;
- // We can just show the surface here as it will still be hidden as the parent is
- // still hidden.
- mTransaction.show(childSurfaceControl);
- if (aspectRatioMismatch) {
- // Clip off ugly navigation bar.
- final Rect crop = calculateSnapshotCrop();
- frame = calculateSnapshotFrame(crop);
- mTransaction.setWindowCrop(childSurfaceControl, crop);
- mTransaction.setPosition(childSurfaceControl, frame.left, frame.top);
- mTmpSnapshotSize.set(crop);
- mTmpDstFrame.set(frame);
- } else {
- frame = null;
- mTmpSnapshotSize.set(0, 0, buffer.getWidth(), buffer.getHeight());
- mTmpDstFrame.set(mFrame);
- mTmpDstFrame.offsetTo(0, 0);
- }
-
- // Scale the mismatch dimensions to fill the task bounds
- mSnapshotMatrix.setRectToRect(mTmpSnapshotSize, mTmpDstFrame, Matrix.ScaleToFit.FILL);
- mTransaction.setMatrix(childSurfaceControl, mSnapshotMatrix, mTmpFloat9);
- mTransaction.setColorSpace(childSurfaceControl, mSnapshot.getColorSpace());
- mTransaction.setBuffer(childSurfaceControl, mSnapshot.getHardwareBuffer());
-
- if (aspectRatioMismatch) {
- GraphicBuffer background = GraphicBuffer.create(mFrame.width(), mFrame.height(),
- PixelFormat.RGBA_8888,
- GraphicBuffer.USAGE_HW_TEXTURE | GraphicBuffer.USAGE_HW_COMPOSER
- | GraphicBuffer.USAGE_SW_WRITE_RARELY);
- // TODO: Support this on HardwareBuffer
- final Canvas c = background.lockCanvas();
- drawBackgroundAndBars(c, frame);
- background.unlockCanvasAndPost(c);
- mTransaction.setBuffer(mSurfaceControl,
- HardwareBuffer.createFromGraphicBuffer(background));
- }
- mTransaction.apply();
- childSurfaceControl.release();
- }
-
- /**
- * Calculates the snapshot crop in snapshot coordinate space.
- *
- * @return crop rect in snapshot coordinate space.
- */
- public Rect calculateSnapshotCrop() {
- final Rect rect = new Rect();
- final HardwareBuffer snapshot = mSnapshot.getHardwareBuffer();
- rect.set(0, 0, snapshot.getWidth(), snapshot.getHeight());
- final Rect insets = mSnapshot.getContentInsets();
-
- final float scaleX = (float) snapshot.getWidth() / mSnapshot.getTaskSize().x;
- final float scaleY = (float) snapshot.getHeight() / mSnapshot.getTaskSize().y;
-
- // Let's remove all system decorations except the status bar, but only if the task is at the
- // very top of the screen.
- final boolean isTop = mTaskBounds.top == 0 && mFrame.top == 0;
- rect.inset((int) (insets.left * scaleX),
- isTop ? 0 : (int) (insets.top * scaleY),
- (int) (insets.right * scaleX),
- (int) (insets.bottom * scaleY));
- return rect;
- }
-
- /**
- * Calculates the snapshot frame in window coordinate space from crop.
- *
- * @param crop rect that is in snapshot coordinate space.
- */
- public Rect calculateSnapshotFrame(Rect crop) {
- final HardwareBuffer snapshot = mSnapshot.getHardwareBuffer();
- final float scaleX = (float) snapshot.getWidth() / mSnapshot.getTaskSize().x;
- final float scaleY = (float) snapshot.getHeight() / mSnapshot.getTaskSize().y;
-
- // Rescale the frame from snapshot to window coordinate space
- final Rect frame = new Rect(0, 0,
- (int) (crop.width() / scaleX + 0.5f),
- (int) (crop.height() / scaleY + 0.5f)
- );
-
- // However, we also need to make space for the navigation bar on the left side.
- frame.offset(mSystemBarInsets.left, 0);
- return frame;
- }
-
- /**
- * Draw status bar and navigation bar background.
- * @hide
- */
- public void drawBackgroundAndBars(Canvas c, Rect frame) {
- final int statusBarHeight = mSystemBarBackgroundPainter.getStatusBarColorViewHeight();
- final boolean fillHorizontally = c.getWidth() > frame.right;
- final boolean fillVertically = c.getHeight() > frame.bottom;
- if (fillHorizontally) {
- c.drawRect(frame.right, alpha(mStatusBarColor) == 0xFF ? statusBarHeight : 0,
- c.getWidth(), fillVertically
- ? frame.bottom
- : c.getHeight(),
- mBackgroundPaint);
- }
- if (fillVertically) {
- c.drawRect(0, frame.bottom, c.getWidth(), c.getHeight(), mBackgroundPaint);
- }
- mSystemBarBackgroundPainter.drawDecors(c, frame);
- }
-
- /**
* Clear window from drawer, must be post on main executor.
*/
private void clearWindowSynced() {
@@ -557,92 +260,4 @@ public class TaskSnapshotWindow {
});
}
}
-
- /**
- * Helper class to draw the background of the system bars in regions the task snapshot isn't
- * filling the window.
- */
- static class SystemBarBackgroundPainter {
- private final Paint mStatusBarPaint = new Paint();
- private final Paint mNavigationBarPaint = new Paint();
- private final int mStatusBarColor;
- private final int mNavigationBarColor;
- private final int mWindowFlags;
- private final int mWindowPrivateFlags;
- private final float mScale;
- private final @InsetsType int mRequestedVisibleTypes;
- private final Rect mSystemBarInsets = new Rect();
-
- SystemBarBackgroundPainter(int windowFlags, int windowPrivateFlags, int appearance,
- TaskDescription taskDescription, float scale,
- @InsetsType int requestedVisibleTypes) {
- mWindowFlags = windowFlags;
- mWindowPrivateFlags = windowPrivateFlags;
- 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(), appearance,
- APPEARANCE_LIGHT_STATUS_BARS,
- taskDescription.getEnsureStatusBarContrastWhenTransparent());
- mNavigationBarColor = DecorView.calculateBarColor(windowFlags,
- FLAG_TRANSLUCENT_NAVIGATION, semiTransparent,
- taskDescription.getNavigationBarColor(), appearance,
- APPEARANCE_LIGHT_NAVIGATION_BARS,
- taskDescription.getEnsureNavigationBarContrastWhenTransparent()
- && context.getResources().getBoolean(R.bool.config_navBarNeedsScrim));
- mStatusBarPaint.setColor(mStatusBarColor);
- mNavigationBarPaint.setColor(mNavigationBarColor);
- mRequestedVisibleTypes = requestedVisibleTypes;
- }
-
- void setInsets(Rect systemBarInsets) {
- mSystemBarInsets.set(systemBarInsets);
- }
-
- int getStatusBarColorViewHeight() {
- final boolean forceBarBackground =
- (mWindowPrivateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS) != 0;
- if (STATUS_BAR_COLOR_VIEW_ATTRIBUTES.isVisible(
- mRequestedVisibleTypes, mStatusBarColor, mWindowFlags, forceBarBackground)) {
- return (int) (mSystemBarInsets.top * mScale);
- } else {
- return 0;
- }
- }
-
- private boolean isNavigationBarColorViewVisible() {
- final boolean forceBarBackground =
- (mWindowPrivateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS) != 0;
- return NAVIGATION_BAR_COLOR_VIEW_ATTRIBUTES.isVisible(
- mRequestedVisibleTypes, mNavigationBarColor, mWindowFlags, forceBarBackground);
- }
-
- void drawDecors(Canvas c, @Nullable Rect alreadyDrawnFrame) {
- drawStatusBarBackground(c, alreadyDrawnFrame, getStatusBarColorViewHeight());
- drawNavigationBarBackground(c);
- }
-
- void drawStatusBarBackground(Canvas c, @Nullable Rect alreadyDrawnFrame,
- int statusBarHeight) {
- if (statusBarHeight > 0 && Color.alpha(mStatusBarColor) != 0
- && (alreadyDrawnFrame == null || c.getWidth() > alreadyDrawnFrame.right)) {
- final int rightInset = (int) (mSystemBarInsets.right * mScale);
- final int left = alreadyDrawnFrame != null ? alreadyDrawnFrame.right : 0;
- c.drawRect(left, 0, c.getWidth() - rightInset, statusBarHeight, mStatusBarPaint);
- }
- }
-
- @VisibleForTesting
- void drawNavigationBarBackground(Canvas c) {
- final Rect navigationBarRect = new Rect();
- getNavigationBarRect(c.getWidth(), c.getHeight(), mSystemBarInsets, navigationBarRect,
- mScale);
- final boolean visible = isNavigationBarColorViewVisible();
- if (visible && Color.alpha(mNavigationBarColor) != 0 && !navigationBarRect.isEmpty()) {
- c.drawRect(navigationBarRect, mNavigationBarPaint);
- }
- }
- }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index e40db4e4dcf2..afefd5dc6344 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -55,6 +55,7 @@ import com.android.wm.shell.desktopmode.DesktopModeStatus;
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
import com.android.wm.shell.transition.Transitions;
+import java.util.Optional;
import java.util.function.Supplier;
/**
@@ -74,11 +75,11 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
private final DisplayController mDisplayController;
private final SyncTransactionQueue mSyncQueue;
private FreeformTaskTransitionStarter mTransitionStarter;
- private DesktopModeController mDesktopModeController;
- private EventReceiver mEventReceiver;
- private InputMonitor mInputMonitor;
+ private Optional<DesktopModeController> mDesktopModeController;
private boolean mTransitionDragActive;
+ private SparseArray<EventReceiver> mEventReceiversByDisplay = new SparseArray<>();
+
private final SparseArray<CaptionWindowDecoration> mWindowDecorByTaskId = new SparseArray<>();
private final DragStartListenerImpl mDragStartListener = new DragStartListenerImpl();
private EventReceiverFactory mEventReceiverFactory = new EventReceiverFactory();
@@ -90,7 +91,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
ShellTaskOrganizer taskOrganizer,
DisplayController displayController,
SyncTransactionQueue syncQueue,
- DesktopModeController desktopModeController) {
+ Optional<DesktopModeController> desktopModeController) {
this(
context,
mainHandler,
@@ -110,7 +111,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
ShellTaskOrganizer taskOrganizer,
DisplayController displayController,
SyncTransactionQueue syncQueue,
- DesktopModeController desktopModeController,
+ Optional<DesktopModeController> desktopModeController,
CaptionWindowDecoration.Factory captionWindowDecorFactory,
Supplier<InputManager> inputManagerSupplier) {
@@ -150,8 +151,15 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
@Override
public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
final CaptionWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId);
+
if (decoration == null) return;
+ int oldDisplayId = decoration.mDisplay.getDisplayId();
+ if (taskInfo.displayId != oldDisplayId) {
+ removeTaskFromEventReceiver(oldDisplayId);
+ incrementEventReceiverTasks(taskInfo.displayId);
+ }
+
decoration.relayout(taskInfo);
}
@@ -195,6 +203,11 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
if (decoration == null) return;
decoration.close();
+ int displayId = taskInfo.displayId;
+ if (mEventReceiversByDisplay.contains(displayId)) {
+ EventReceiver eventReceiver = mEventReceiversByDisplay.get(displayId);
+ removeTaskFromEventReceiver(displayId);
+ }
}
private class CaptionTouchEventListener implements
@@ -234,10 +247,10 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
} else if (id == R.id.caption_handle) {
decoration.createHandleMenu();
} else if (id == R.id.desktop_button) {
- mDesktopModeController.setDesktopModeActive(true);
+ mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(true));
decoration.closeHandleMenu();
} else if (id == R.id.fullscreen_button) {
- mDesktopModeController.setDesktopModeActive(false);
+ mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(false));
decoration.closeHandleMenu();
decoration.setButtonVisibility();
}
@@ -292,9 +305,9 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
*/
private void handleEventForMove(MotionEvent e) {
RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
- int windowingMode = mDesktopModeController
- .getDisplayAreaWindowingMode(taskInfo.displayId);
- if (windowingMode == WINDOWING_MODE_FULLSCREEN) {
+ if (mDesktopModeController.isPresent()
+ && mDesktopModeController.get().getDisplayAreaWindowingMode(taskInfo.displayId)
+ == WINDOWING_MODE_FULLSCREEN) {
return;
}
switch (e.getActionMasked()) {
@@ -319,7 +332,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
if (e.getRawY(dragPointerIdx) <= statusBarHeight
&& DesktopModeStatus.isActive(mContext)) {
- mDesktopModeController.setDesktopModeActive(false);
+ mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(false));
}
break;
}
@@ -329,8 +342,12 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
// InputEventReceiver to listen for touch input outside of caption bounds
class EventReceiver extends InputEventReceiver {
- EventReceiver(InputChannel channel, Looper looper) {
+ private InputMonitor mInputMonitor;
+ private int mTasksOnDisplay;
+ EventReceiver(InputMonitor inputMonitor, InputChannel channel, Looper looper) {
super(channel, looper);
+ mInputMonitor = inputMonitor;
+ mTasksOnDisplay = 1;
}
@Override
@@ -338,15 +355,62 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
boolean handled = false;
if (event instanceof MotionEvent) {
handled = true;
- CaptionWindowDecorViewModel.this.handleReceivedMotionEvent((MotionEvent) event);
+ CaptionWindowDecorViewModel.this
+ .handleReceivedMotionEvent((MotionEvent) event, mInputMonitor);
}
finishInputEvent(event, handled);
}
+
+ @Override
+ public void dispose() {
+ if (mInputMonitor != null) {
+ mInputMonitor.dispose();
+ mInputMonitor = null;
+ }
+ super.dispose();
+ }
+
+ private void incrementTaskNumber() {
+ mTasksOnDisplay++;
+ }
+
+ private void decrementTaskNumber() {
+ mTasksOnDisplay--;
+ }
+
+ private int getTasksOnDisplay() {
+ return mTasksOnDisplay;
+ }
+ }
+
+ /**
+ * Check if an EventReceiver exists on a particular display.
+ * If it does, increment its task count. Otherwise, create one for that display.
+ * @param displayId the display to check against
+ */
+ private void incrementEventReceiverTasks(int displayId) {
+ if (mEventReceiversByDisplay.contains(displayId)) {
+ EventReceiver eventReceiver = mEventReceiversByDisplay.get(displayId);
+ eventReceiver.incrementTaskNumber();
+ } else {
+ createInputChannel(displayId);
+ }
+ }
+
+ // If all tasks on this display are gone, we don't need to monitor its input.
+ private void removeTaskFromEventReceiver(int displayId) {
+ if (!mEventReceiversByDisplay.contains(displayId)) return;
+ EventReceiver eventReceiver = mEventReceiversByDisplay.get(displayId);
+ if (eventReceiver == null) return;
+ eventReceiver.decrementTaskNumber();
+ if (eventReceiver.getTasksOnDisplay() == 0) {
+ disposeInputChannel(displayId);
+ }
}
class EventReceiverFactory {
- EventReceiver create(InputChannel channel, Looper looper) {
- return new EventReceiver(channel, looper);
+ EventReceiver create(InputMonitor inputMonitor, InputChannel channel, Looper looper) {
+ return new EventReceiver(inputMonitor, channel, looper);
}
}
@@ -355,14 +419,14 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
*
* @param ev the {@link MotionEvent} received by {@link EventReceiver}
*/
- private void handleReceivedMotionEvent(MotionEvent ev) {
+ private void handleReceivedMotionEvent(MotionEvent ev, InputMonitor inputMonitor) {
if (!DesktopModeStatus.isActive(mContext)) {
handleCaptionThroughStatusBar(ev);
}
handleEventOutsideFocusedCaption(ev);
// Prevent status bar from reacting to a caption drag.
if (mTransitionDragActive && !DesktopModeStatus.isActive(mContext)) {
- mInputMonitor.pilferPointers();
+ inputMonitor.pilferPointers();
}
}
@@ -381,6 +445,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
}
}
+
/**
* Perform caption actions if not able to through normal means.
* Turn on desktop mode if handle is dragged below status bar.
@@ -407,7 +472,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
int statusBarHeight = mDisplayController
.getDisplayLayout(focusedDecor.mTaskInfo.displayId).stableInsets().top;
if (ev.getY() > statusBarHeight) {
- mDesktopModeController.setDesktopModeActive(true);
+ mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(true));
return;
}
}
@@ -434,9 +499,25 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
return focusedDecor;
}
+ private void createInputChannel(int displayId) {
+ InputManager inputManager = mInputManagerSupplier.get();
+ InputMonitor inputMonitor =
+ inputManager.monitorGestureInput("caption-touch", mContext.getDisplayId());
+ EventReceiver eventReceiver = mEventReceiverFactory.create(
+ inputMonitor, inputMonitor.getInputChannel(), Looper.myLooper());
+ mEventReceiversByDisplay.put(displayId, eventReceiver);
+ }
+
+ private void disposeInputChannel(int displayId) {
+ EventReceiver eventReceiver = mEventReceiversByDisplay.removeReturnOld(displayId);
+ if (eventReceiver != null) {
+ eventReceiver.dispose();
+ }
+ }
+
private boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) {
if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) return true;
- return DesktopModeStatus.IS_SUPPORTED
+ return DesktopModeStatus.isAnyEnabled()
&& taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD
&& mDisplayController.getDisplayContext(taskInfo.displayId)
.getResources().getConfiguration().smallestScreenWidthDp >= 600;
@@ -472,14 +553,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
windowDecoration.setCaptionListeners(touchEventListener, touchEventListener);
windowDecoration.setDragResizeCallback(taskPositioner);
windowDecoration.relayout(taskInfo, startT, finishT);
- if (mInputMonitor == null) {
- InputManager inputManager = mInputManagerSupplier.get();
- mInputMonitor =
- inputManager.monitorGestureInput("caption-touch", mContext.getDisplayId());
- mEventReceiver =
- mEventReceiverFactory.create(
- mInputMonitor.getInputChannel(), Looper.myLooper());
- }
+ incrementEventReceiverTasks(taskInfo.displayId);
}
private class DragStartListenerImpl implements TaskPositioner.DragStartListener {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index 7ecb3f3f6355..92154968855f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -251,7 +251,9 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
}
final int captionHeight = loadDimensionPixelSize(resources, params.mCaptionHeightId);
- final int captionWidth = loadDimensionPixelSize(resources, params.mCaptionWidthId);
+ final int captionWidth = params.mCaptionWidthId == Resources.ID_NULL
+ ? taskBounds.width()
+ : loadDimensionPixelSize(resources, params.mCaptionWidthId);
startT.setPosition(
mCaptionContainerSurface,
diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml b/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
index 08913c6fde53..27fc381a10d1 100644
--- a/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
+++ b/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
@@ -19,6 +19,8 @@
<option name="run-command" value="locksettings set-disabled false" />
<!-- restart launcher to activate TAPL -->
<option name="run-command" value="setprop ro.test_harness 1 ; am force-stop com.google.android.apps.nexuslauncher" />
+ <!-- Ensure output directory is empty at the start -->
+ <option name="run-command" value="rm -rf /sdcard/flicker" />
</target_preparer>
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true"/>
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt
index 6370df4a0c35..8465678524f4 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt
@@ -20,10 +20,10 @@ import android.app.Instrumentation
import android.platform.test.annotations.Presubmit
import androidx.test.platform.app.InstrumentationRegistry
import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.server.wm.flicker.FlickerBuilderProvider
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.entireScreenCovered
+import com.android.server.wm.flicker.junit.FlickerBuilderProvider
import com.android.server.wm.flicker.navBarLayerIsVisibleAtStartAndEnd
import com.android.server.wm.flicker.navBarLayerPositionAtStartAndEnd
import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
@@ -45,12 +45,12 @@ import org.junit.Test
abstract class BaseTest
@JvmOverloads
constructor(
- protected val testSpec: FlickerTestParameter,
+ protected val flicker: FlickerTest,
protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation(),
protected val tapl: LauncherInstrumentation = LauncherInstrumentation()
) {
init {
- testSpec.setIsTablet(
+ flicker.scenario.setIsTablet(
WindowManagerStateHelper(instrumentation, clearCacheAfterParsing = false)
.currentState
.wmState
@@ -68,13 +68,13 @@ constructor(
@FlickerBuilderProvider
fun buildFlicker(): FlickerBuilder {
return FlickerBuilder(instrumentation).apply {
- setup { testSpec.setIsTablet(wmHelper.currentState.wmState.isTablet) }
+ setup { flicker.scenario.setIsTablet(wmHelper.currentState.wmState.isTablet) }
transition()
}
}
/** Checks that all parts of the screen are covered during the transition */
- @Presubmit @Test open fun entireScreenCovered() = testSpec.entireScreenCovered()
+ @Presubmit @Test open fun entireScreenCovered() = flicker.entireScreenCovered()
/**
* Checks that the [ComponentNameMatcher.NAV_BAR] layer is visible during the whole transition
@@ -82,8 +82,8 @@ constructor(
@Presubmit
@Test
open fun navBarLayerIsVisibleAtStartAndEnd() {
- Assume.assumeFalse(testSpec.isTablet)
- testSpec.navBarLayerIsVisibleAtStartAndEnd()
+ Assume.assumeFalse(flicker.scenario.isTablet)
+ flicker.navBarLayerIsVisibleAtStartAndEnd()
}
/**
@@ -93,8 +93,8 @@ constructor(
@Presubmit
@Test
open fun navBarLayerPositionAtStartAndEnd() {
- Assume.assumeFalse(testSpec.isTablet)
- testSpec.navBarLayerPositionAtStartAndEnd()
+ Assume.assumeFalse(flicker.scenario.isTablet)
+ flicker.navBarLayerPositionAtStartAndEnd()
}
/**
@@ -105,8 +105,8 @@ constructor(
@Presubmit
@Test
open fun navBarWindowIsAlwaysVisible() {
- Assume.assumeFalse(testSpec.isTablet)
- testSpec.navBarWindowIsAlwaysVisible()
+ Assume.assumeFalse(flicker.scenario.isTablet)
+ flicker.navBarWindowIsAlwaysVisible()
}
/**
@@ -115,8 +115,8 @@ constructor(
@Presubmit
@Test
open fun taskBarLayerIsVisibleAtStartAndEnd() {
- Assume.assumeTrue(testSpec.isTablet)
- testSpec.taskBarLayerIsVisibleAtStartAndEnd()
+ Assume.assumeTrue(flicker.scenario.isTablet)
+ flicker.taskBarLayerIsVisibleAtStartAndEnd()
}
/**
@@ -127,8 +127,8 @@ constructor(
@Presubmit
@Test
open fun taskBarWindowIsAlwaysVisible() {
- Assume.assumeTrue(testSpec.isTablet)
- testSpec.taskBarWindowIsAlwaysVisible()
+ Assume.assumeTrue(flicker.scenario.isTablet)
+ flicker.taskBarWindowIsAlwaysVisible()
}
/**
@@ -137,8 +137,7 @@ constructor(
*/
@Presubmit
@Test
- open fun statusBarLayerIsVisibleAtStartAndEnd() =
- testSpec.statusBarLayerIsVisibleAtStartAndEnd()
+ open fun statusBarLayerIsVisibleAtStartAndEnd() = flicker.statusBarLayerIsVisibleAtStartAndEnd()
/**
* Checks the position of the [ComponentNameMatcher.STATUS_BAR] at the start and end of the
@@ -146,7 +145,7 @@ constructor(
*/
@Presubmit
@Test
- open fun statusBarLayerPositionAtStartAndEnd() = testSpec.statusBarLayerPositionAtStartAndEnd()
+ open fun statusBarLayerPositionAtStartAndEnd() = flicker.statusBarLayerPositionAtStartAndEnd()
/**
* Checks that the [ComponentNameMatcher.STATUS_BAR] window is visible during the whole
@@ -154,7 +153,7 @@ constructor(
*/
@Presubmit
@Test
- open fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible()
+ open fun statusBarWindowIsAlwaysVisible() = flicker.statusBarWindowIsAlwaysVisible()
/**
* Checks that all layers that are visible on the trace, are visible for at least 2 consecutive
@@ -163,7 +162,7 @@ constructor(
@Presubmit
@Test
open fun visibleLayersShownMoreThanOneConsecutiveEntry() {
- testSpec.assertLayers { this.visibleLayersShownMoreThanOneConsecutiveEntry() }
+ flicker.assertLayers { this.visibleLayersShownMoreThanOneConsecutiveEntry() }
}
/**
@@ -173,6 +172,6 @@ constructor(
@Presubmit
@Test
open fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
- testSpec.assertWm { this.visibleWindowsShownMoreThanOneConsecutiveEntry() }
+ flicker.assertWm { this.visibleWindowsShownMoreThanOneConsecutiveEntry() }
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
index 8765ad1596a5..51869140e8fa 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
@@ -18,23 +18,23 @@
package com.android.wm.shell.flicker
-import android.view.Surface
-import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.helpers.WindowUtils
import com.android.server.wm.flicker.traces.layers.LayerTraceEntrySubject
import com.android.server.wm.flicker.traces.layers.LayersTraceSubject
import com.android.server.wm.traces.common.IComponentMatcher
import com.android.server.wm.traces.common.region.Region
+import com.android.server.wm.traces.common.service.PlatformConsts
-fun FlickerTestParameter.appPairsDividerIsVisibleAtEnd() {
+fun FlickerTest.appPairsDividerIsVisibleAtEnd() {
assertLayersEnd { this.isVisible(APP_PAIR_SPLIT_DIVIDER_COMPONENT) }
}
-fun FlickerTestParameter.appPairsDividerIsInvisibleAtEnd() {
+fun FlickerTest.appPairsDividerIsInvisibleAtEnd() {
assertLayersEnd { this.notContains(APP_PAIR_SPLIT_DIVIDER_COMPONENT) }
}
-fun FlickerTestParameter.appPairsDividerBecomesVisible() {
+fun FlickerTest.appPairsDividerBecomesVisible() {
assertLayers {
this.isInvisible(DOCKED_STACK_DIVIDER_COMPONENT)
.then()
@@ -42,7 +42,7 @@ fun FlickerTestParameter.appPairsDividerBecomesVisible() {
}
}
-fun FlickerTestParameter.splitScreenEntered(
+fun FlickerTest.splitScreenEntered(
component1: IComponentMatcher,
component2: IComponentMatcher,
fromOtherApp: Boolean,
@@ -69,7 +69,7 @@ fun FlickerTestParameter.splitScreenEntered(
splitScreenDividerIsVisibleAtEnd()
}
-fun FlickerTestParameter.splitScreenDismissed(
+fun FlickerTest.splitScreenDismissed(
component1: IComponentMatcher,
component2: IComponentMatcher,
toHome: Boolean
@@ -87,27 +87,27 @@ fun FlickerTestParameter.splitScreenDismissed(
splitScreenDividerIsInvisibleAtEnd()
}
-fun FlickerTestParameter.splitScreenDividerIsVisibleAtStart() {
+fun FlickerTest.splitScreenDividerIsVisibleAtStart() {
assertLayersStart { this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) }
}
-fun FlickerTestParameter.splitScreenDividerIsVisibleAtEnd() {
+fun FlickerTest.splitScreenDividerIsVisibleAtEnd() {
assertLayersEnd { this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) }
}
-fun FlickerTestParameter.splitScreenDividerIsInvisibleAtStart() {
+fun FlickerTest.splitScreenDividerIsInvisibleAtStart() {
assertLayersStart { this.isInvisible(SPLIT_SCREEN_DIVIDER_COMPONENT) }
}
-fun FlickerTestParameter.splitScreenDividerIsInvisibleAtEnd() {
+fun FlickerTest.splitScreenDividerIsInvisibleAtEnd() {
assertLayersEnd { this.isInvisible(SPLIT_SCREEN_DIVIDER_COMPONENT) }
}
-fun FlickerTestParameter.splitScreenDividerBecomesVisible() {
+fun FlickerTest.splitScreenDividerBecomesVisible() {
layerBecomesVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
}
-fun FlickerTestParameter.splitScreenDividerBecomesInvisible() {
+fun FlickerTest.splitScreenDividerBecomesInvisible() {
assertLayers {
this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
.then()
@@ -115,23 +115,23 @@ fun FlickerTestParameter.splitScreenDividerBecomesInvisible() {
}
}
-fun FlickerTestParameter.layerBecomesVisible(component: IComponentMatcher) {
+fun FlickerTest.layerBecomesVisible(component: IComponentMatcher) {
assertLayers { this.isInvisible(component).then().isVisible(component) }
}
-fun FlickerTestParameter.layerBecomesInvisible(component: IComponentMatcher) {
+fun FlickerTest.layerBecomesInvisible(component: IComponentMatcher) {
assertLayers { this.isVisible(component).then().isInvisible(component) }
}
-fun FlickerTestParameter.layerIsVisibleAtEnd(component: IComponentMatcher) {
+fun FlickerTest.layerIsVisibleAtEnd(component: IComponentMatcher) {
assertLayersEnd { this.isVisible(component) }
}
-fun FlickerTestParameter.layerKeepVisible(component: IComponentMatcher) {
+fun FlickerTest.layerKeepVisible(component: IComponentMatcher) {
assertLayers { this.isVisible(component) }
}
-fun FlickerTestParameter.splitAppLayerBoundsBecomesVisible(
+fun FlickerTest.splitAppLayerBoundsBecomesVisible(
component: IComponentMatcher,
landscapePosLeft: Boolean,
portraitPosTop: Boolean
@@ -145,12 +145,12 @@ fun FlickerTestParameter.splitAppLayerBoundsBecomesVisible(
component,
landscapePosLeft,
portraitPosTop,
- endRotation
+ scenario.endRotation
)
}
}
-fun FlickerTestParameter.splitAppLayerBoundsBecomesVisibleByDrag(component: IComponentMatcher) {
+fun FlickerTest.splitAppLayerBoundsBecomesVisibleByDrag(component: IComponentMatcher) {
assertLayers {
this.notContains(SPLIT_SCREEN_DIVIDER_COMPONENT.or(component), isOptional = true)
.then()
@@ -161,7 +161,7 @@ fun FlickerTestParameter.splitAppLayerBoundsBecomesVisibleByDrag(component: ICom
}
}
-fun FlickerTestParameter.splitAppLayerBoundsBecomesInvisible(
+fun FlickerTest.splitAppLayerBoundsBecomesInvisible(
component: IComponentMatcher,
landscapePosLeft: Boolean,
portraitPosTop: Boolean
@@ -171,7 +171,7 @@ fun FlickerTestParameter.splitAppLayerBoundsBecomesInvisible(
component,
landscapePosLeft,
portraitPosTop,
- endRotation
+ scenario.endRotation
)
.then()
.isVisible(component, true)
@@ -180,27 +180,37 @@ fun FlickerTestParameter.splitAppLayerBoundsBecomesInvisible(
}
}
-fun FlickerTestParameter.splitAppLayerBoundsIsVisibleAtEnd(
+fun FlickerTest.splitAppLayerBoundsIsVisibleAtEnd(
component: IComponentMatcher,
landscapePosLeft: Boolean,
portraitPosTop: Boolean
) {
assertLayersEnd {
- splitAppLayerBoundsSnapToDivider(component, landscapePosLeft, portraitPosTop, endRotation)
+ splitAppLayerBoundsSnapToDivider(
+ component,
+ landscapePosLeft,
+ portraitPosTop,
+ scenario.endRotation
+ )
}
}
-fun FlickerTestParameter.splitAppLayerBoundsKeepVisible(
+fun FlickerTest.splitAppLayerBoundsKeepVisible(
component: IComponentMatcher,
landscapePosLeft: Boolean,
portraitPosTop: Boolean
) {
assertLayers {
- splitAppLayerBoundsSnapToDivider(component, landscapePosLeft, portraitPosTop, endRotation)
+ splitAppLayerBoundsSnapToDivider(
+ component,
+ landscapePosLeft,
+ portraitPosTop,
+ scenario.endRotation
+ )
}
}
-fun FlickerTestParameter.splitAppLayerBoundsChanges(
+fun FlickerTest.splitAppLayerBoundsChanges(
component: IComponentMatcher,
landscapePosLeft: Boolean,
portraitPosTop: Boolean
@@ -211,14 +221,14 @@ fun FlickerTestParameter.splitAppLayerBoundsChanges(
component,
landscapePosLeft,
portraitPosTop,
- endRotation
+ scenario.endRotation
)
} else {
this.splitAppLayerBoundsSnapToDivider(
component,
landscapePosLeft,
portraitPosTop,
- endRotation
+ scenario.endRotation
)
.then()
.isInvisible(component)
@@ -227,7 +237,7 @@ fun FlickerTestParameter.splitAppLayerBoundsChanges(
component,
landscapePosLeft,
portraitPosTop,
- endRotation
+ scenario.endRotation
)
}
}
@@ -237,7 +247,7 @@ fun LayersTraceSubject.splitAppLayerBoundsSnapToDivider(
component: IComponentMatcher,
landscapePosLeft: Boolean,
portraitPosTop: Boolean,
- rotation: Int
+ rotation: PlatformConsts.Rotation
): LayersTraceSubject {
return invoke("splitAppLayerBoundsSnapToDivider") {
it.splitAppLayerBoundsSnapToDivider(component, landscapePosLeft, portraitPosTop, rotation)
@@ -248,7 +258,7 @@ fun LayerTraceEntrySubject.splitAppLayerBoundsSnapToDivider(
component: IComponentMatcher,
landscapePosLeft: Boolean,
portraitPosTop: Boolean,
- rotation: Int
+ rotation: PlatformConsts.Rotation
): LayerTraceEntrySubject {
val displayBounds = WindowUtils.getDisplayBounds(rotation)
return invoke {
@@ -292,7 +302,7 @@ fun LayerTraceEntrySubject.splitAppLayerBoundsSnapToDivider(
}
}
-fun FlickerTestParameter.appWindowBecomesVisible(component: IComponentMatcher) {
+fun FlickerTest.appWindowBecomesVisible(component: IComponentMatcher) {
assertWm {
this.isAppWindowInvisible(component)
.then()
@@ -304,39 +314,39 @@ fun FlickerTestParameter.appWindowBecomesVisible(component: IComponentMatcher) {
}
}
-fun FlickerTestParameter.appWindowBecomesInvisible(component: IComponentMatcher) {
+fun FlickerTest.appWindowBecomesInvisible(component: IComponentMatcher) {
assertWm { this.isAppWindowVisible(component).then().isAppWindowInvisible(component) }
}
-fun FlickerTestParameter.appWindowIsVisibleAtStart(component: IComponentMatcher) {
+fun FlickerTest.appWindowIsVisibleAtStart(component: IComponentMatcher) {
assertWmStart { this.isAppWindowVisible(component) }
}
-fun FlickerTestParameter.appWindowIsVisibleAtEnd(component: IComponentMatcher) {
+fun FlickerTest.appWindowIsVisibleAtEnd(component: IComponentMatcher) {
assertWmEnd { this.isAppWindowVisible(component) }
}
-fun FlickerTestParameter.appWindowIsInvisibleAtStart(component: IComponentMatcher) {
+fun FlickerTest.appWindowIsInvisibleAtStart(component: IComponentMatcher) {
assertWmStart { this.isAppWindowInvisible(component) }
}
-fun FlickerTestParameter.appWindowIsInvisibleAtEnd(component: IComponentMatcher) {
+fun FlickerTest.appWindowIsInvisibleAtEnd(component: IComponentMatcher) {
assertWmEnd { this.isAppWindowInvisible(component) }
}
-fun FlickerTestParameter.appWindowIsNotContainAtStart(component: IComponentMatcher) {
+fun FlickerTest.appWindowIsNotContainAtStart(component: IComponentMatcher) {
assertWmStart { this.notContains(component) }
}
-fun FlickerTestParameter.appWindowKeepVisible(component: IComponentMatcher) {
+fun FlickerTest.appWindowKeepVisible(component: IComponentMatcher) {
assertWm { this.isAppWindowVisible(component) }
}
-fun FlickerTestParameter.dockedStackDividerIsVisibleAtEnd() {
+fun FlickerTest.dockedStackDividerIsVisibleAtEnd() {
assertLayersEnd { this.isVisible(DOCKED_STACK_DIVIDER_COMPONENT) }
}
-fun FlickerTestParameter.dockedStackDividerBecomesVisible() {
+fun FlickerTest.dockedStackDividerBecomesVisible() {
assertLayers {
this.isInvisible(DOCKED_STACK_DIVIDER_COMPONENT)
.then()
@@ -344,7 +354,7 @@ fun FlickerTestParameter.dockedStackDividerBecomesVisible() {
}
}
-fun FlickerTestParameter.dockedStackDividerBecomesInvisible() {
+fun FlickerTest.dockedStackDividerBecomesInvisible() {
assertLayers {
this.isVisible(DOCKED_STACK_DIVIDER_COMPONENT)
.then()
@@ -352,12 +362,12 @@ fun FlickerTestParameter.dockedStackDividerBecomesInvisible() {
}
}
-fun FlickerTestParameter.dockedStackDividerNotExistsAtEnd() {
+fun FlickerTest.dockedStackDividerNotExistsAtEnd() {
assertLayersEnd { this.notContains(DOCKED_STACK_DIVIDER_COMPONENT) }
}
-fun FlickerTestParameter.appPairsPrimaryBoundsIsVisibleAtEnd(
- rotation: Int,
+fun FlickerTest.appPairsPrimaryBoundsIsVisibleAtEnd(
+ rotation: PlatformConsts.Rotation,
primaryComponent: IComponentMatcher
) {
assertLayersEnd {
@@ -366,8 +376,8 @@ fun FlickerTestParameter.appPairsPrimaryBoundsIsVisibleAtEnd(
}
}
-fun FlickerTestParameter.dockedStackPrimaryBoundsIsVisibleAtEnd(
- rotation: Int,
+fun FlickerTest.dockedStackPrimaryBoundsIsVisibleAtEnd(
+ rotation: PlatformConsts.Rotation,
primaryComponent: IComponentMatcher
) {
assertLayersEnd {
@@ -376,8 +386,8 @@ fun FlickerTestParameter.dockedStackPrimaryBoundsIsVisibleAtEnd(
}
}
-fun FlickerTestParameter.appPairsSecondaryBoundsIsVisibleAtEnd(
- rotation: Int,
+fun FlickerTest.appPairsSecondaryBoundsIsVisibleAtEnd(
+ rotation: PlatformConsts.Rotation,
secondaryComponent: IComponentMatcher
) {
assertLayersEnd {
@@ -386,8 +396,8 @@ fun FlickerTestParameter.appPairsSecondaryBoundsIsVisibleAtEnd(
}
}
-fun FlickerTestParameter.dockedStackSecondaryBoundsIsVisibleAtEnd(
- rotation: Int,
+fun FlickerTest.dockedStackSecondaryBoundsIsVisibleAtEnd(
+ rotation: PlatformConsts.Rotation,
secondaryComponent: IComponentMatcher
) {
assertLayersEnd {
@@ -396,38 +406,38 @@ fun FlickerTestParameter.dockedStackSecondaryBoundsIsVisibleAtEnd(
}
}
-fun getPrimaryRegion(dividerRegion: Region, rotation: Int): Region {
+fun getPrimaryRegion(dividerRegion: Region, rotation: PlatformConsts.Rotation): Region {
val displayBounds = WindowUtils.getDisplayBounds(rotation)
- return if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) {
+ return if (rotation.isRotated()) {
Region.from(
0,
0,
- displayBounds.bounds.right,
- dividerRegion.bounds.top + WindowUtils.dockedStackDividerInset
+ dividerRegion.bounds.left + WindowUtils.dockedStackDividerInset,
+ displayBounds.bounds.bottom
)
} else {
Region.from(
0,
0,
- dividerRegion.bounds.left + WindowUtils.dockedStackDividerInset,
- displayBounds.bounds.bottom
+ displayBounds.bounds.right,
+ dividerRegion.bounds.top + WindowUtils.dockedStackDividerInset
)
}
}
-fun getSecondaryRegion(dividerRegion: Region, rotation: Int): Region {
+fun getSecondaryRegion(dividerRegion: Region, rotation: PlatformConsts.Rotation): Region {
val displayBounds = WindowUtils.getDisplayBounds(rotation)
- return if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) {
+ return if (rotation.isRotated()) {
Region.from(
+ dividerRegion.bounds.right - WindowUtils.dockedStackDividerInset,
0,
- dividerRegion.bounds.bottom - WindowUtils.dockedStackDividerInset,
displayBounds.bounds.right,
displayBounds.bounds.bottom
)
} else {
Region.from(
- dividerRegion.bounds.right - WindowUtils.dockedStackDividerInset,
0,
+ dividerRegion.bounds.bottom - WindowUtils.dockedStackDividerInset,
displayBounds.bounds.right,
displayBounds.bounds.bottom
)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt
index 0fc2004ce7f9..996b677470fe 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt
@@ -21,21 +21,21 @@ import android.app.NotificationManager
import android.content.Context
import android.content.pm.PackageManager
import android.os.ServiceManager
-import android.view.Surface
import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiObject2
import androidx.test.uiautomator.Until
-import com.android.server.wm.flicker.Flicker
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
+import com.android.server.wm.flicker.IFlickerTestData
import com.android.server.wm.flicker.helpers.LaunchBubbleHelper
import com.android.server.wm.flicker.helpers.SYSTEMUI_PACKAGE
+import com.android.server.wm.traces.common.service.PlatformConsts
import com.android.wm.shell.flicker.BaseTest
import org.junit.runners.Parameterized
/** Base configurations for Bubble flicker tests */
-abstract class BaseBubbleScreen(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
+abstract class BaseBubbleScreen(flicker: FlickerTest) : BaseTest(flicker) {
protected val context: Context = instrumentation.context
protected val testApp = LaunchBubbleHelper(instrumentation)
@@ -79,17 +79,18 @@ abstract class BaseBubbleScreen(testSpec: FlickerTestParameter) : BaseTest(testS
}
}
- protected fun Flicker.waitAndGetAddBubbleBtn(): UiObject2? =
+ protected fun IFlickerTestData.waitAndGetAddBubbleBtn(): UiObject2? =
device.wait(Until.findObject(By.text("Add Bubble")), FIND_OBJECT_TIMEOUT)
- protected fun Flicker.waitAndGetCancelAllBtn(): UiObject2? =
+ protected fun IFlickerTestData.waitAndGetCancelAllBtn(): UiObject2? =
device.wait(Until.findObject(By.text("Cancel All Bubble")), FIND_OBJECT_TIMEOUT)
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0))
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
+ )
}
const val FIND_OBJECT_TIMEOUT = 2000L
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreen.kt
index ab721173763e..7fc12f06f530 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreen.kt
@@ -25,9 +25,9 @@ import android.view.WindowManager
import androidx.test.filters.RequiresDevice
import androidx.test.uiautomator.By
import androidx.test.uiautomator.Until
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
@@ -45,7 +45,7 @@ import org.junit.runners.Parameterized
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-open class DismissBubbleScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) {
+open class DismissBubbleScreen(flicker: FlickerTest) : BaseBubbleScreen(flicker) {
private val wm = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
private val displaySize = DisplayMetrics()
@@ -72,7 +72,7 @@ open class DismissBubbleScreen(testSpec: FlickerTestParameter) : BaseBubbleScree
@Presubmit
@Test
open fun testAppIsAlwaysVisible() {
- testSpec.assertLayers { this.isVisible(testApp) }
+ flicker.assertLayers { this.isVisible(testApp) }
}
/** {@inheritDoc} */
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreen.kt
index 226eab89920f..0cda626f8286 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreen.kt
@@ -20,9 +20,9 @@ import android.platform.test.annotations.Presubmit
import androidx.test.filters.RequiresDevice
import androidx.test.uiautomator.By
import androidx.test.uiautomator.Until
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
@@ -42,7 +42,7 @@ import org.junit.runners.Parameterized
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-open class ExpandBubbleScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) {
+open class ExpandBubbleScreen(flicker: FlickerTest) : BaseBubbleScreen(flicker) {
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit
@@ -64,6 +64,6 @@ open class ExpandBubbleScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen
@Presubmit
@Test
open fun testAppIsAlwaysVisible() {
- testSpec.assertLayers { this.isVisible(testApp) }
+ flicker.assertLayers { this.isVisible(testApp) }
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleFromLockScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleFromLockScreen.kt
index 47167b8b469e..04b1bdd3fd46 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleFromLockScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleFromLockScreen.kt
@@ -23,9 +23,9 @@ import android.view.WindowManager
import androidx.test.filters.RequiresDevice
import androidx.test.uiautomator.By
import androidx.test.uiautomator.Until
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
@@ -43,7 +43,7 @@ import org.junit.runners.Parameterized
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-class LaunchBubbleFromLockScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) {
+class LaunchBubbleFromLockScreen(flicker: FlickerTest) : BaseBubbleScreen(flicker) {
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit
@@ -88,7 +88,7 @@ class LaunchBubbleFromLockScreen(testSpec: FlickerTestParameter) : BaseBubbleScr
@FlakyTest(bugId = 242088970)
@Test
fun testAppIsVisibleAtEnd() {
- testSpec.assertLayersEnd { this.isVisible(testApp) }
+ flicker.assertLayersEnd { this.isVisible(testApp) }
}
/** {@inheritDoc} */
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt
index b86599913649..9b4e39c42d11 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt
@@ -19,9 +19,9 @@ package com.android.wm.shell.flicker.bubble
import android.platform.test.annotations.RequiresDevice
import androidx.test.uiautomator.By
import androidx.test.uiautomator.Until
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
@@ -40,7 +40,7 @@ import org.junit.runners.Parameterized
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-open class LaunchBubbleScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) {
+open class LaunchBubbleScreen(flicker: FlickerTest) : BaseBubbleScreen(flicker) {
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit
@@ -59,6 +59,6 @@ open class LaunchBubbleScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen
@Test
open fun testAppIsAlwaysVisible() {
- testSpec.assertLayers { this.isVisible(testApp) }
+ flicker.assertLayers { this.isVisible(testApp) }
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt
index bf4d7d4e7734..b3a2ad309b27 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt
@@ -22,10 +22,10 @@ import androidx.test.filters.RequiresDevice
import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiObject2
import androidx.test.uiautomator.Until
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import org.junit.Assume
import org.junit.Before
import org.junit.Test
@@ -45,7 +45,7 @@ import org.junit.runners.Parameterized
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-open class MultiBubblesScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) {
+open class MultiBubblesScreen(flicker: FlickerTest) : BaseBubbleScreen(flicker) {
@Before
open fun before() {
@@ -87,6 +87,6 @@ open class MultiBubblesScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen
@Presubmit
@Test
open fun testAppIsAlwaysVisible() {
- testSpec.assertLayers { this.isVisible(testApp) }
+ flicker.assertLayers { this.isVisible(testApp) }
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreenShellTransit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreenShellTransit.kt
index 57adeab7b070..191f4fa893f9 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreenShellTransit.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreenShellTransit.kt
@@ -18,9 +18,9 @@ package com.android.wm.shell.flicker.bubble
import android.platform.test.annotations.FlakyTest
import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import org.junit.Assume
import org.junit.Before
import org.junit.runner.RunWith
@@ -30,8 +30,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FlakyTest(bugId = 217777115)
-class MultiBubblesScreenShellTransit(testSpec: FlickerTestParameter) :
- MultiBubblesScreen(testSpec) {
+class MultiBubblesScreenShellTransit(flicker: FlickerTest) : MultiBubblesScreen(flicker) {
@Before
override fun before() {
Assume.assumeTrue(isShellTransitionsEnabled)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
index 7546a55c08fa..5e898e8710cd 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
@@ -18,15 +18,15 @@ package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
-import android.view.Surface
import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule
import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome
+import com.android.server.wm.traces.common.service.PlatformConsts
import org.junit.Assume
import org.junit.FixMethodOrder
import org.junit.Test
@@ -59,7 +59,7 @@ import org.junit.runners.Parameterized
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@FlakyTest(bugId = 238367575)
-class AutoEnterPipOnGoToHomeTest(testSpec: FlickerTestParameter) : EnterPipTest(testSpec) {
+class AutoEnterPipOnGoToHomeTest(flicker: FlickerTest) : EnterPipTest(flicker) {
/** Defines the transition used to run the test */
override val transition: FlickerBuilder.() -> Unit
get() = {
@@ -73,7 +73,7 @@ class AutoEnterPipOnGoToHomeTest(testSpec: FlickerTestParameter) : EnterPipTest(
// close gracefully so that onActivityUnpinned() can be called before force exit
pipApp.closePipWindow(wmHelper)
- setRotation(Surface.ROTATION_0)
+ setRotation(PlatformConsts.Rotation.ROTATION_0)
RemoveAllTasksButHomeRule.removeAllTasksButHome()
pipApp.exit(wmHelper)
}
@@ -83,7 +83,7 @@ class AutoEnterPipOnGoToHomeTest(testSpec: FlickerTestParameter) : EnterPipTest(
@FlakyTest(bugId = 256863309)
@Test
override fun pipLayerReduces() {
- testSpec.assertLayers {
+ flicker.assertLayers {
val pipLayerList = this.layers { pipApp.layerMatchesAnyOf(it) && it.isVisible }
pipLayerList.zipWithNext { previous, current ->
current.visibleRegion.notBiggerThan(previous.visibleRegion.region)
@@ -96,8 +96,8 @@ class AutoEnterPipOnGoToHomeTest(testSpec: FlickerTestParameter) : EnterPipTest(
@Test
fun pipLayerMovesTowardsRightBottomCorner() {
// in gestural nav the swipe makes PiP first go upwards
- Assume.assumeFalse(testSpec.isGesturalNavigation)
- testSpec.assertLayers {
+ Assume.assumeFalse(flicker.scenario.isGesturalNavigation)
+ flicker.assertLayers {
val pipLayerList = this.layers { pipApp.layerMatchesAnyOf(it) && it.isVisible }
// Pip animates towards the right bottom corner, but because it is being resized at the
// same time, it is possible it shrinks first quickly below the default position and get
@@ -112,7 +112,7 @@ class AutoEnterPipOnGoToHomeTest(testSpec: FlickerTestParameter) : EnterPipTest(
@Test
override fun focusChanges() {
// in gestural nav the focus goes to different activity on swipe up
- Assume.assumeFalse(testSpec.isGesturalNavigation)
+ Assume.assumeFalse(flicker.scenario.isGesturalNavigation)
super.focusChanges()
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
index c8aa6d20c91b..79feeaa3c222 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
@@ -17,14 +17,14 @@
package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
-import android.view.Surface
import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule
+import com.android.server.wm.traces.common.service.PlatformConsts
import org.junit.Assume
import org.junit.FixMethodOrder
import org.junit.Test
@@ -56,7 +56,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class EnterPipOnUserLeaveHintTest(testSpec: FlickerTestParameter) : EnterPipTest(testSpec) {
+class EnterPipOnUserLeaveHintTest(flicker: FlickerTest) : EnterPipTest(flicker) {
/** Defines the transition used to run the test */
override val transition: FlickerBuilder.() -> Unit
get() = {
@@ -68,7 +68,7 @@ class EnterPipOnUserLeaveHintTest(testSpec: FlickerTestParameter) : EnterPipTest
pipApp.enableEnterPipOnUserLeaveHint()
}
teardown {
- setRotation(Surface.ROTATION_0)
+ setRotation(PlatformConsts.Rotation.ROTATION_0)
RemoveAllTasksButHomeRule.removeAllTasksButHome()
pipApp.exit(wmHelper)
}
@@ -78,10 +78,10 @@ class EnterPipOnUserLeaveHintTest(testSpec: FlickerTestParameter) : EnterPipTest
@Presubmit
@Test
override fun pipAppLayerAlwaysVisible() {
- if (!testSpec.isGesturalNavigation) super.pipAppLayerAlwaysVisible()
+ if (!flicker.scenario.isGesturalNavigation) super.pipAppLayerAlwaysVisible()
else {
// pip layer in gesture nav will disappear during transition
- testSpec.assertLayers {
+ flicker.assertLayers {
this.isVisible(pipApp).then().isInvisible(pipApp).then().isVisible(pipApp)
}
}
@@ -91,7 +91,7 @@ class EnterPipOnUserLeaveHintTest(testSpec: FlickerTestParameter) : EnterPipTest
@Test
override fun pipLayerReduces() {
// in gestural nav the pip enters through alpha animation
- Assume.assumeFalse(testSpec.isGesturalNavigation)
+ Assume.assumeFalse(flicker.scenario.isGesturalNavigation)
super.pipLayerReduces()
}
@@ -99,7 +99,7 @@ class EnterPipOnUserLeaveHintTest(testSpec: FlickerTestParameter) : EnterPipTest
@Test
override fun focusChanges() {
// in gestural nav the focus goes to different activity on swipe up
- Assume.assumeFalse(testSpec.isGesturalNavigation)
+ Assume.assumeFalse(flicker.scenario.isGesturalNavigation)
super.focusChanges()
}
@@ -112,11 +112,11 @@ class EnterPipOnUserLeaveHintTest(testSpec: FlickerTestParameter) : EnterPipTest
@Presubmit
@Test
override fun pipLayerRemainInsideVisibleBounds() {
- if (!testSpec.isGesturalNavigation) super.pipLayerRemainInsideVisibleBounds()
+ if (!flicker.scenario.isGesturalNavigation) super.pipLayerRemainInsideVisibleBounds()
else {
// pip layer in gesture nav will disappear during transition
- testSpec.assertLayersStart { this.visibleRegion(pipApp).coversAtMost(displayBounds) }
- testSpec.assertLayersEnd { this.visibleRegion(pipApp).coversAtMost(displayBounds) }
+ flicker.assertLayersStart { this.visibleRegion(pipApp).coversAtMost(displayBounds) }
+ flicker.assertLayersEnd { this.visibleRegion(pipApp).coversAtMost(displayBounds) }
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
index 2b629e73e750..1a76142330a7 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
@@ -17,16 +17,16 @@
package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
-import android.view.Surface
import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule
import com.android.server.wm.traces.common.ComponentNameMatcher
+import com.android.server.wm.traces.common.service.PlatformConsts
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -57,7 +57,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class EnterPipTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
+open class EnterPipTest(flicker: FlickerTest) : PipTransition(flicker) {
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit
@@ -68,7 +68,7 @@ open class EnterPipTest(testSpec: FlickerTestParameter) : PipTransition(testSpec
pipApp.launchViaIntent(wmHelper)
}
teardown {
- setRotation(Surface.ROTATION_0)
+ setRotation(PlatformConsts.Rotation.ROTATION_0)
RemoveAllTasksButHomeRule.removeAllTasksButHome()
pipApp.exit(wmHelper)
}
@@ -79,16 +79,14 @@ open class EnterPipTest(testSpec: FlickerTestParameter) : PipTransition(testSpec
@Presubmit
@Test
open fun pipAppWindowAlwaysVisible() {
- testSpec.assertWm { this.isAppWindowVisible(pipApp) }
+ flicker.assertWm { this.isAppWindowVisible(pipApp) }
}
- /**
- * Checks [pipApp] layer remains visible throughout the animation
- */
+ /** Checks [pipApp] layer remains visible throughout the animation */
@Presubmit
@Test
open fun pipAppLayerAlwaysVisible() {
- testSpec.assertLayers { this.isVisible(pipApp) }
+ flicker.assertLayers { this.isVisible(pipApp) }
}
/**
@@ -98,7 +96,7 @@ open class EnterPipTest(testSpec: FlickerTestParameter) : PipTransition(testSpec
@Presubmit
@Test
fun pipWindowRemainInsideVisibleBounds() {
- testSpec.assertWmVisibleRegion(pipApp) { coversAtMost(displayBounds) }
+ flicker.assertWmVisibleRegion(pipApp) { coversAtMost(displayBounds) }
}
/**
@@ -108,14 +106,14 @@ open class EnterPipTest(testSpec: FlickerTestParameter) : PipTransition(testSpec
@Presubmit
@Test
open fun pipLayerRemainInsideVisibleBounds() {
- testSpec.assertLayersVisibleRegion(pipApp) { coversAtMost(displayBounds) }
+ flicker.assertLayersVisibleRegion(pipApp) { coversAtMost(displayBounds) }
}
/** Checks that the visible region of [pipApp] always reduces during the animation */
@Presubmit
@Test
open fun pipLayerReduces() {
- testSpec.assertLayers {
+ flicker.assertLayers {
val pipLayerList = this.layers { pipApp.layerMatchesAnyOf(it) && it.isVisible }
pipLayerList.zipWithNext { previous, current ->
current.visibleRegion.notBiggerThan(previous.visibleRegion.region)
@@ -127,7 +125,7 @@ open class EnterPipTest(testSpec: FlickerTestParameter) : PipTransition(testSpec
@Presubmit
@Test
fun pipWindowBecomesPinned() {
- testSpec.assertWm {
+ flicker.assertWm {
invoke("pipWindowIsNotPinned") { it.isNotPinned(pipApp) }
.then()
.invoke("pipWindowIsPinned") { it.isPinned(pipApp) }
@@ -138,7 +136,7 @@ open class EnterPipTest(testSpec: FlickerTestParameter) : PipTransition(testSpec
@Presubmit
@Test
fun launcherLayerBecomesVisible() {
- testSpec.assertLayers {
+ flicker.assertLayers {
isInvisible(ComponentNameMatcher.LAUNCHER)
.then()
.isVisible(ComponentNameMatcher.LAUNCHER)
@@ -152,21 +150,22 @@ open class EnterPipTest(testSpec: FlickerTestParameter) : PipTransition(testSpec
@Presubmit
@Test
open fun focusChanges() {
- testSpec.assertEventLog { this.focusChanges(pipApp.`package`, "NexusLauncherActivity") }
+ flicker.assertEventLog { this.focusChanges(pipApp.`package`, "NexusLauncherActivity") }
}
companion object {
/**
* Creates the test configurations.
*
- * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
- * screen orientation and navigation modes.
+ * See [FlickerTestFactory.nonRotationTests] for configuring repetitions, screen orientation
+ * and navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0))
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
+ )
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
index b4594dee2c73..2b90243de5a6 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
@@ -19,22 +19,22 @@ package com.android.wm.shell.flicker.pip
import android.app.Activity
import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
-import android.view.Surface
import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.entireScreenCovered
import com.android.server.wm.flicker.helpers.FixedOrientationAppHelper
import com.android.server.wm.flicker.helpers.WindowUtils
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.navBarLayerPositionAtStartAndEnd
import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule
import com.android.server.wm.flicker.testapp.ActivityOptions.Pip.ACTION_ENTER_PIP
import com.android.server.wm.flicker.testapp.ActivityOptions.PortraitOnlyActivity.EXTRA_FIXED_ORIENTATION
import com.android.server.wm.traces.common.ComponentNameMatcher
+import com.android.server.wm.traces.common.service.PlatformConsts
import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_LANDSCAPE
import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_PORTRAIT
import org.junit.Assume
@@ -51,32 +51,31 @@ import org.junit.runners.Parameterized
* To run this test: `atest WMShellFlickerTests:EnterPipToOtherOrientationTest`
*
* Actions:
+ * ```
* Launch [testApp] on a fixed portrait orientation
* Launch [pipApp] on a fixed landscape orientation
* Broadcast action [ACTION_ENTER_PIP] to enter pip mode
- *
+ * ```
* Notes:
+ * ```
* 1. Some default assertions (e.g., nav bar, status bar and screen covered)
* are inherited [PipTransition]
* 2. Part of the test setup occurs automatically via
* [com.android.server.wm.flicker.TransitionRunnerWithRules],
* including configuring navigation mode, initial orientation and ensuring no
* apps are running before setup
+ * ```
*/
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class EnterPipToOtherOrientationTest(
- testSpec: FlickerTestParameter
-) : PipTransition(testSpec) {
+class EnterPipToOtherOrientationTest(flicker: FlickerTest) : PipTransition(flicker) {
private val testApp = FixedOrientationAppHelper(instrumentation)
- private val startingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_90)
- private val endingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_0)
+ private val startingBounds = WindowUtils.getDisplayBounds(PlatformConsts.Rotation.ROTATION_90)
+ private val endingBounds = WindowUtils.getDisplayBounds(PlatformConsts.Rotation.ROTATION_0)
- /**
- * Defines the transition used to run the test
- */
+ /** Defines the transition used to run the test */
override val transition: FlickerBuilder.() -> Unit
get() = {
setup {
@@ -85,19 +84,18 @@ class EnterPipToOtherOrientationTest(
// Launch a portrait only app on the fullscreen stack
testApp.launchViaIntent(
- wmHelper, stringExtras = mapOf(
- EXTRA_FIXED_ORIENTATION to ORIENTATION_PORTRAIT.toString()
- )
+ wmHelper,
+ stringExtras = mapOf(EXTRA_FIXED_ORIENTATION to ORIENTATION_PORTRAIT.toString())
)
// Launch the PiP activity fixed as landscape
pipApp.launchViaIntent(
- wmHelper, stringExtras = mapOf(
- EXTRA_FIXED_ORIENTATION to ORIENTATION_LANDSCAPE.toString()
- )
+ wmHelper,
+ stringExtras =
+ mapOf(EXTRA_FIXED_ORIENTATION to ORIENTATION_LANDSCAPE.toString())
)
}
teardown {
- setRotation(Surface.ROTATION_0)
+ setRotation(PlatformConsts.Rotation.ROTATION_0)
RemoveAllTasksButHomeRule.removeAllTasksButHome()
pipApp.exit(wmHelper)
testApp.exit(wmHelper)
@@ -107,7 +105,8 @@ class EnterPipToOtherOrientationTest(
// in portrait
broadcastActionTrigger.doAction(ACTION_ENTER_PIP)
// during rotation the status bar becomes invisible and reappears at the end
- wmHelper.StateSyncBuilder()
+ wmHelper
+ .StateSyncBuilder()
.withPipShown()
.withNavOrTaskBarVisible()
.withStatusBarVisible()
@@ -116,21 +115,21 @@ class EnterPipToOtherOrientationTest(
}
/**
- * This test is not compatible with Tablets. When using [Activity.setRequestedOrientation]
- * to fix a orientation, Tablets instead keep the same orientation and add letterboxes
+ * This test is not compatible with Tablets. When using [Activity.setRequestedOrientation] to
+ * fix a orientation, Tablets instead keep the same orientation and add letterboxes
*/
@Before
fun setup() {
- Assume.assumeFalse(testSpec.isTablet)
+ Assume.assumeFalse(flicker.scenario.isTablet)
}
/**
- * Checks that the [ComponentNameMatcher.NAV_BAR] has the correct position at
- * the start and end of the transition
+ * Checks that the [ComponentNameMatcher.NAV_BAR] has the correct position at the start and end
+ * of the transition
*/
@FlakyTest
@Test
- override fun navBarLayerPositionAtStartAndEnd() = testSpec.navBarLayerPositionAtStartAndEnd()
+ override fun navBarLayerPositionAtStartAndEnd() = flicker.navBarLayerPositionAtStartAndEnd()
/**
* Checks that all parts of the screen are covered at the start and end of the transition
@@ -139,7 +138,7 @@ class EnterPipToOtherOrientationTest(
*/
@Presubmit
@Test
- fun entireScreenCoveredAtStartAndEnd() = testSpec.entireScreenCovered(allStates = false)
+ fun entireScreenCoveredAtStartAndEnd() = flicker.entireScreenCovered(allStates = false)
@FlakyTest(bugId = 251219769)
@Test
@@ -147,89 +146,65 @@ class EnterPipToOtherOrientationTest(
super.entireScreenCovered()
}
- /**
- * Checks [pipApp] window remains visible and on top throughout the transition
- */
+ /** Checks [pipApp] window remains visible and on top throughout the transition */
@Presubmit
@Test
fun pipAppWindowIsAlwaysOnTop() {
- testSpec.assertWm {
- isAppWindowOnTop(pipApp)
- }
+ flicker.assertWm { isAppWindowOnTop(pipApp) }
}
- /**
- * Checks that [testApp] window is not visible at the start
- */
+ /** Checks that [testApp] window is not visible at the start */
@Presubmit
@Test
fun testAppWindowInvisibleOnStart() {
- testSpec.assertWmStart {
- isAppWindowInvisible(testApp)
- }
+ flicker.assertWmStart { isAppWindowInvisible(testApp) }
}
- /**
- * Checks that [testApp] window is visible at the end
- */
+ /** Checks that [testApp] window is visible at the end */
@Presubmit
@Test
fun testAppWindowVisibleOnEnd() {
- testSpec.assertWmEnd {
- isAppWindowVisible(testApp)
- }
+ flicker.assertWmEnd { isAppWindowVisible(testApp) }
}
- /**
- * Checks that [testApp] layer is not visible at the start
- */
+ /** Checks that [testApp] layer is not visible at the start */
@Presubmit
@Test
fun testAppLayerInvisibleOnStart() {
- testSpec.assertLayersStart {
- isInvisible(testApp)
- }
+ flicker.assertLayersStart { isInvisible(testApp) }
}
- /**
- * Checks that [testApp] layer is visible at the end
- */
+ /** Checks that [testApp] layer is visible at the end */
@Presubmit
@Test
fun testAppLayerVisibleOnEnd() {
- testSpec.assertLayersEnd {
- isVisible(testApp)
- }
+ flicker.assertLayersEnd { isVisible(testApp) }
}
/**
- * Checks that the visible region of [pipApp] covers the full display area at the start of
- * the transition
+ * Checks that the visible region of [pipApp] covers the full display area at the start of the
+ * transition
*/
@Presubmit
@Test
fun pipAppLayerCoversFullScreenOnStart() {
- testSpec.assertLayersStart {
- visibleRegion(pipApp).coversExactly(startingBounds)
- }
+ flicker.assertLayersStart { visibleRegion(pipApp).coversExactly(startingBounds) }
}
/**
- * Checks that the visible region of [testApp] plus the visible region of [pipApp]
- * cover the full display area at the end of the transition
+ * Checks that the visible region of [testApp] plus the visible region of [pipApp] cover the
+ * full display area at the end of the transition
*/
@Presubmit
@Test
fun testAppPlusPipLayerCoversFullScreenOnEnd() {
- testSpec.assertLayersEnd {
+ flicker.assertLayersEnd {
val pipRegion = visibleRegion(pipApp).region
- visibleRegion(testApp)
- .plus(pipRegion)
- .coversExactly(endingBounds)
+ visibleRegion(testApp).plus(pipRegion).coversExactly(endingBounds)
}
}
- /** {@inheritDoc} */
+ /** {@inheritDoc} */
@Presubmit
@Test
override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
@@ -239,16 +214,15 @@ class EnterPipToOtherOrientationTest(
/**
* Creates the test configurations.
*
- * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
- * repetitions, screen orientation and navigation modes.
+ * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(
- supportedRotations = listOf(Surface.ROTATION_0)
- )
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
+ )
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt
index 1dc03b9b8900..7466916a798a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt
@@ -17,12 +17,12 @@
package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
-import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.helpers.SimpleAppHelper
import org.junit.Test
/** Base class for pip expand tests */
-abstract class ExitPipToAppTransition(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
+abstract class ExitPipToAppTransition(flicker: FlickerTest) : PipTransition(flicker) {
protected val testApp = SimpleAppHelper(instrumentation)
/**
@@ -32,7 +32,7 @@ abstract class ExitPipToAppTransition(testSpec: FlickerTestParameter) : PipTrans
@Presubmit
@Test
open fun pipAppWindowRemainInsideVisibleBounds() {
- testSpec.assertWmVisibleRegion(pipApp) { coversAtMost(displayBounds) }
+ flicker.assertWmVisibleRegion(pipApp) { coversAtMost(displayBounds) }
}
/**
@@ -42,7 +42,7 @@ abstract class ExitPipToAppTransition(testSpec: FlickerTestParameter) : PipTrans
@Presubmit
@Test
open fun pipAppLayerRemainInsideVisibleBounds() {
- testSpec.assertLayersVisibleRegion(pipApp) { coversAtMost(displayBounds) }
+ flicker.assertLayersVisibleRegion(pipApp) { coversAtMost(displayBounds) }
}
/**
@@ -52,7 +52,7 @@ abstract class ExitPipToAppTransition(testSpec: FlickerTestParameter) : PipTrans
@Presubmit
@Test
open fun showBothAppWindowsThenHidePip() {
- testSpec.assertWm {
+ flicker.assertWm {
// when the activity is STOPPING, sometimes it becomes invisible in an entry before
// the window, sometimes in the same entry. This occurs because we log 1x per frame
// thus we ignore activity here
@@ -71,7 +71,7 @@ abstract class ExitPipToAppTransition(testSpec: FlickerTestParameter) : PipTrans
@Presubmit
@Test
open fun showBothAppLayersThenHidePip() {
- testSpec.assertLayers {
+ flicker.assertLayers {
isVisible(testApp).isVisible(pipApp).then().isInvisible(testApp).isVisible(pipApp)
}
}
@@ -83,7 +83,7 @@ abstract class ExitPipToAppTransition(testSpec: FlickerTestParameter) : PipTrans
@Presubmit
@Test
open fun testPlusPipAppsCoverFullScreenAtStart() {
- testSpec.assertLayersStart {
+ flicker.assertLayersStart {
val pipRegion = visibleRegion(pipApp).region
visibleRegion(testApp).plus(pipRegion).coversExactly(displayBounds)
}
@@ -96,14 +96,14 @@ abstract class ExitPipToAppTransition(testSpec: FlickerTestParameter) : PipTrans
@Presubmit
@Test
open fun pipAppCoversFullScreenAtEnd() {
- testSpec.assertLayersEnd { visibleRegion(pipApp).coversExactly(displayBounds) }
+ flicker.assertLayersEnd { visibleRegion(pipApp).coversExactly(displayBounds) }
}
/** Checks that the visible region of [pipApp] always expands during the animation */
@Presubmit
@Test
open fun pipLayerExpands() {
- testSpec.assertLayers {
+ flicker.assertLayers {
val pipLayerList = this.layers { pipApp.layerMatchesAnyOf(it) && it.isVisible }
pipLayerList.zipWithNext { previous, current ->
current.visibleRegion.coversAtLeast(previous.visibleRegion.region)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt
index 3b8bb90988f9..1b5c227d37e4 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt
@@ -17,20 +17,20 @@
package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.traces.common.ComponentNameMatcher.Companion.LAUNCHER
+import com.android.server.wm.traces.common.service.PlatformConsts
import org.junit.Test
/** Base class for exiting pip (closing pip window) without returning to the app */
-abstract class ExitPipTransition(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
+abstract class ExitPipTransition(flicker: FlickerTest) : PipTransition(flicker) {
override val transition: FlickerBuilder.() -> Unit
get() = buildTransition {
- setup { this.setRotation(testSpec.startRotation) }
- teardown { this.setRotation(Surface.ROTATION_0) }
+ setup { this.setRotation(flicker.scenario.startRotation) }
+ teardown { this.setRotation(PlatformConsts.Rotation.ROTATION_0) }
}
/**
@@ -45,16 +45,16 @@ abstract class ExitPipTransition(testSpec: FlickerTestParameter) : PipTransition
// When Shell transition is enabled, we change the windowing mode at start, but
// update the visibility after the transition is finished, so we can't check isNotPinned
// and isAppWindowInvisible in the same assertion block.
- testSpec.assertWm {
+ flicker.assertWm {
this.invoke("hasPipWindow") {
it.isPinned(pipApp).isAppWindowVisible(pipApp).isAppWindowOnTop(pipApp)
}
.then()
.invoke("!hasPipWindow") { it.isNotPinned(pipApp).isAppWindowNotOnTop(pipApp) }
}
- testSpec.assertWmEnd { isAppWindowInvisible(pipApp) }
+ flicker.assertWmEnd { isAppWindowInvisible(pipApp) }
} else {
- testSpec.assertWm {
+ flicker.assertWm {
this.invoke("hasPipWindow") { it.isPinned(pipApp).isAppWindowVisible(pipApp) }
.then()
.invoke("!hasPipWindow") { it.isNotPinned(pipApp).isAppWindowInvisible(pipApp) }
@@ -69,7 +69,7 @@ abstract class ExitPipTransition(testSpec: FlickerTestParameter) : PipTransition
@Presubmit
@Test
open fun pipLayerBecomesInvisible() {
- testSpec.assertLayers {
+ flicker.assertLayers {
this.isVisible(pipApp)
.isVisible(LAUNCHER)
.then()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt
index 6bf7e8c847ee..1420f8ce653a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt
@@ -18,12 +18,12 @@ package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
-import android.view.Surface
import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import com.android.server.wm.traces.common.service.PlatformConsts
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -36,30 +36,29 @@ import org.junit.runners.Parameterized
* To run this test: `atest WMShellFlickerTests:ExitPipViaExpandButtonClickTest`
*
* Actions:
+ * ```
* Launch an app in pip mode [pipApp],
* Launch another full screen mode [testApp]
* Expand [pipApp] app to full screen by clicking on the pip window and
* then on the expand button
- *
+ * ```
* Notes:
+ * ```
* 1. Some default assertions (e.g., nav bar, status bar and screen covered)
* are inherited [PipTransition]
* 2. Part of the test setup occurs automatically via
* [com.android.server.wm.flicker.TransitionRunnerWithRules],
* including configuring navigation mode, initial orientation and ensuring no
* apps are running before setup
+ * ```
*/
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ExitPipViaExpandButtonClickTest(
- testSpec: FlickerTestParameter
-) : ExitPipToAppTransition(testSpec) {
+class ExitPipViaExpandButtonClickTest(flicker: FlickerTest) : ExitPipToAppTransition(flicker) {
- /**
- * Defines the transition used to run the test
- */
+ /** Defines the transition used to run the test */
override val transition: FlickerBuilder.() -> Unit
get() = buildTransition {
setup {
@@ -70,34 +69,29 @@ class ExitPipViaExpandButtonClickTest(
// This will bring PipApp to fullscreen
pipApp.expandPipWindowToApp(wmHelper)
// Wait until the other app is no longer visible
- wmHelper.StateSyncBuilder()
- .withWindowSurfaceDisappeared(testApp)
- .waitForAndVerify()
+ wmHelper.StateSyncBuilder().withWindowSurfaceDisappeared(testApp).waitForAndVerify()
}
}
- /** {@inheritDoc} */
- @Presubmit
- @Test
- override fun entireScreenCovered() = super.entireScreenCovered()
+ /** {@inheritDoc} */
+ @Presubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
- /** {@inheritDoc} */
- @FlakyTest(bugId = 197726610)
- @Test
- override fun pipLayerExpands() = super.pipLayerExpands()
+ /** {@inheritDoc} */
+ @FlakyTest(bugId = 197726610) @Test override fun pipLayerExpands() = super.pipLayerExpands()
companion object {
/**
* Creates the test configurations.
*
- * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
- * repetitions, screen orientation and navigation modes.
+ * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
- supportedRotations = listOf(Surface.ROTATION_0))
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
+ )
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt
index 3356d3e2ab2b..dffbe7e8aef1 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt
@@ -18,13 +18,13 @@ package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
-import android.view.Surface
import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import com.android.server.wm.traces.common.service.PlatformConsts
import org.junit.Assume
import org.junit.FixMethodOrder
import org.junit.Test
@@ -57,7 +57,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ExitPipViaIntentTest(testSpec: FlickerTestParameter) : ExitPipToAppTransition(testSpec) {
+class ExitPipViaIntentTest(flicker: FlickerTest) : ExitPipToAppTransition(flicker) {
/** Defines the transition used to run the test */
override val transition: FlickerBuilder.() -> Unit
@@ -74,10 +74,8 @@ class ExitPipViaIntentTest(testSpec: FlickerTestParameter) : ExitPipToAppTransit
}
}
- /** {@inheritDoc} */
- @Presubmit
- @Test
- override fun entireScreenCovered() = super.entireScreenCovered()
+ /** {@inheritDoc} */
+ @Presubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
/** {@inheritDoc} */
@Presubmit
@@ -113,14 +111,15 @@ class ExitPipViaIntentTest(testSpec: FlickerTestParameter) : ExitPipToAppTransit
/**
* Creates the test configurations.
*
- * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
- * screen orientation and navigation modes.
+ * See [FlickerTestFactory.nonRotationTests] for configuring repetitions, screen orientation
+ * and navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0))
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
+ )
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt
index d195abb2aaec..232c025e60a9 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt
@@ -17,12 +17,12 @@
package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
-import android.view.Surface
import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import com.android.server.wm.traces.common.service.PlatformConsts
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -54,7 +54,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ExitPipWithDismissButtonTest(testSpec: FlickerTestParameter) : ExitPipTransition(testSpec) {
+class ExitPipWithDismissButtonTest(flicker: FlickerTest) : ExitPipTransition(flicker) {
override val transition: FlickerBuilder.() -> Unit
get() = {
@@ -69,21 +69,22 @@ class ExitPipWithDismissButtonTest(testSpec: FlickerTestParameter) : ExitPipTran
@Presubmit
@Test
fun focusChanges() {
- testSpec.assertEventLog { this.focusChanges("PipMenuView", "NexusLauncherActivity") }
+ flicker.assertEventLog { this.focusChanges("PipMenuView", "NexusLauncherActivity") }
}
companion object {
/**
* Creates the test configurations.
*
- * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
- * screen orientation and navigation modes.
+ * See [FlickerTestFactory.nonRotationTests] for configuring repetitions, screen orientation
+ * and navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0))
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
+ )
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt
index f7a244717141..dbbfdcc8803a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt
@@ -17,13 +17,13 @@
package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
-import android.view.Surface
import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.traces.common.ComponentNameMatcher
+import com.android.server.wm.traces.common.service.PlatformConsts
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -54,7 +54,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ExitPipWithSwipeDownTest(testSpec: FlickerTestParameter) : ExitPipTransition(testSpec) {
+class ExitPipWithSwipeDownTest(flicker: FlickerTest) : ExitPipTransition(flicker) {
override val transition: FlickerBuilder.() -> Unit
get() = {
super.transition(this)
@@ -64,7 +64,7 @@ class ExitPipWithSwipeDownTest(testSpec: FlickerTestParameter) : ExitPipTransiti
val pipCenterY = pipRegion.centerY()
val displayCenterX = device.displayWidth / 2
val barComponent =
- if (testSpec.isTablet) {
+ if (flicker.scenario.isTablet) {
ComponentNameMatcher.TASK_BAR
} else {
ComponentNameMatcher.NAV_BAR
@@ -92,21 +92,22 @@ class ExitPipWithSwipeDownTest(testSpec: FlickerTestParameter) : ExitPipTransiti
@Presubmit
@Test
fun focusDoesNotChange() {
- testSpec.assertEventLog { this.focusDoesNotChange() }
+ flicker.assertEventLog { this.focusDoesNotChange() }
}
companion object {
/**
* Creates the test configurations.
*
- * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
- * screen orientation and navigation modes.
+ * See [FlickerTestFactory.nonRotationTests] for configuring repetitions, screen orientation
+ * and navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0))
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
+ )
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
index fa5ce5bbc389..f213cc96ecdb 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
@@ -17,13 +17,13 @@
package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.FlakyTest
-import android.view.Surface
import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.traces.common.ComponentNameMatcher
+import com.android.server.wm.traces.common.service.PlatformConsts
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -36,28 +36,27 @@ import org.junit.runners.Parameterized
* To run this test: `atest WMShellFlickerTests:ExpandPipOnDoubleClickTest`
*
* Actions:
+ * ```
* Launch an app in pip mode [pipApp],
* Expand [pipApp] app to its maximum pip size by double clicking on it
- *
+ * ```
* Notes:
+ * ```
* 1. Some default assertions (e.g., nav bar, status bar and screen covered)
* are inherited [PipTransition]
* 2. Part of the test setup occurs automatically via
* [com.android.server.wm.flicker.TransitionRunnerWithRules],
* including configuring navigation mode, initial orientation and ensuring no
* apps are running before setup
+ * ```
*/
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ExpandPipOnDoubleClickTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
+class ExpandPipOnDoubleClickTest(flicker: FlickerTest) : PipTransition(flicker) {
override val transition: FlickerBuilder.() -> Unit
- get() = buildTransition {
- transitions {
- pipApp.doubleClickPipWindow(wmHelper)
- }
- }
+ get() = buildTransition { transitions { pipApp.doubleClickPipWindow(wmHelper) } }
/**
* Checks that the pip app window remains inside the display bounds throughout the whole
@@ -66,9 +65,7 @@ class ExpandPipOnDoubleClickTest(testSpec: FlickerTestParameter) : PipTransition
@FlakyTest(bugId = 249308003)
@Test
fun pipWindowRemainInsideVisibleBounds() {
- testSpec.assertWmVisibleRegion(pipApp) {
- coversAtMost(displayBounds)
- }
+ flicker.assertWmVisibleRegion(pipApp) { coversAtMost(displayBounds) }
}
/**
@@ -78,40 +75,28 @@ class ExpandPipOnDoubleClickTest(testSpec: FlickerTestParameter) : PipTransition
@FlakyTest(bugId = 249308003)
@Test
fun pipLayerRemainInsideVisibleBounds() {
- testSpec.assertLayersVisibleRegion(pipApp) {
- coversAtMost(displayBounds)
- }
+ flicker.assertLayersVisibleRegion(pipApp) { coversAtMost(displayBounds) }
}
- /**
- * Checks [pipApp] window remains visible throughout the animation
- */
+ /** Checks [pipApp] window remains visible throughout the animation */
@FlakyTest(bugId = 249308003)
@Test
fun pipWindowIsAlwaysVisible() {
- testSpec.assertWm {
- isAppWindowVisible(pipApp)
- }
+ flicker.assertWm { isAppWindowVisible(pipApp) }
}
- /**
- * Checks [pipApp] layer remains visible throughout the animation
- */
+ /** Checks [pipApp] layer remains visible throughout the animation */
@FlakyTest(bugId = 249308003)
@Test
fun pipLayerIsAlwaysVisible() {
- testSpec.assertLayers {
- isVisible(pipApp)
- }
+ flicker.assertLayers { isVisible(pipApp) }
}
- /**
- * Checks that the visible region of [pipApp] always expands during the animation
- */
+ /** Checks that the visible region of [pipApp] always expands during the animation */
@FlakyTest(bugId = 249308003)
@Test
fun pipLayerExpands() {
- testSpec.assertLayers {
+ flicker.assertLayers {
val pipLayerList = this.layers { pipApp.layerMatchesAnyOf(it) && it.isVisible }
pipLayerList.zipWithNext { previous, current ->
current.visibleRegion.coversAtLeast(previous.visibleRegion.region)
@@ -122,7 +107,7 @@ class ExpandPipOnDoubleClickTest(testSpec: FlickerTestParameter) : PipTransition
@FlakyTest(bugId = 249308003)
@Test
fun pipSameAspectRatio() {
- testSpec.assertLayers {
+ flicker.assertLayers {
val pipLayerList = this.layers { pipApp.layerMatchesAnyOf(it) && it.isVisible }
pipLayerList.zipWithNext { previous, current ->
current.visibleRegion.isSameAspectRatio(previous.visibleRegion)
@@ -130,37 +115,25 @@ class ExpandPipOnDoubleClickTest(testSpec: FlickerTestParameter) : PipTransition
}
}
- /**
- * Checks [pipApp] window remains pinned throughout the animation
- */
+ /** Checks [pipApp] window remains pinned throughout the animation */
@FlakyTest(bugId = 249308003)
@Test
fun windowIsAlwaysPinned() {
- testSpec.assertWm {
- this.invoke("hasPipWindow") { it.isPinned(pipApp) }
- }
+ flicker.assertWm { this.invoke("hasPipWindow") { it.isPinned(pipApp) } }
}
- /**
- * Checks [ComponentMatcher.LAUNCHER] layer remains visible throughout the animation
- */
+ /** Checks [ComponentMatcher.LAUNCHER] layer remains visible throughout the animation */
@FlakyTest(bugId = 249308003)
@Test
fun launcherIsAlwaysVisible() {
- testSpec.assertLayers {
- isVisible(ComponentNameMatcher.LAUNCHER)
- }
+ flicker.assertLayers { isVisible(ComponentNameMatcher.LAUNCHER) }
}
- /**
- * Checks that the focus doesn't change between windows during the transition
- */
+ /** Checks that the focus doesn't change between windows during the transition */
@FlakyTest(bugId = 216306753)
@Test
fun focusDoesNotChange() {
- testSpec.assertEventLog {
- this.focusDoesNotChange()
- }
+ flicker.assertEventLog { this.focusDoesNotChange() }
}
@FlakyTest(bugId = 216306753)
@@ -233,16 +206,15 @@ class ExpandPipOnDoubleClickTest(testSpec: FlickerTestParameter) : PipTransition
/**
* Creates the test configurations.
*
- * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
- * repetitions, screen orientation and navigation modes.
+ * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(
- supportedRotations = listOf(Surface.ROTATION_0)
- )
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
+ )
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt
index bcd01a414fd3..34f6659ca36e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt
@@ -17,40 +17,32 @@
package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Postsubmit
-import android.view.Surface
import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import com.android.server.wm.traces.common.service.PlatformConsts
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
-/**
- * Test expanding a pip window via pinch out gesture.
- */
+/** Test expanding a pip window via pinch out gesture. */
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ExpandPipOnPinchOpenTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
+class ExpandPipOnPinchOpenTest(flicker: FlickerTest) : PipTransition(flicker) {
override val transition: FlickerBuilder.() -> Unit
- get() = buildTransition {
- transitions {
- pipApp.pinchOpenPipWindow(wmHelper, 0.4f, 30)
- }
- }
+ get() = buildTransition { transitions { pipApp.pinchOpenPipWindow(wmHelper, 0.4f, 30) } }
- /**
- * Checks that the visible region area of [pipApp] always increases during the animation.
- */
+ /** Checks that the visible region area of [pipApp] always increases during the animation. */
@Postsubmit
@Test
fun pipLayerAreaIncreases() {
- testSpec.assertLayers {
+ flicker.assertLayers {
val pipLayerList = this.layers { pipApp.layerMatchesAnyOf(it) && it.isVisible }
pipLayerList.zipWithNext { previous, current ->
previous.visibleRegion.notBiggerThan(current.visibleRegion.region)
@@ -62,16 +54,15 @@ class ExpandPipOnPinchOpenTest(testSpec: FlickerTestParameter) : PipTransition(t
/**
* Creates the test configurations.
*
- * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
- * repetitions, screen orientation and navigation modes.
+ * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(
- supportedRotations = listOf(Surface.ROTATION_0)
- )
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
+ )
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt
index 0c0228ede58c..e9847fae00fe 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt
@@ -18,11 +18,11 @@ package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
import android.platform.test.annotations.RequiresDevice
-import android.view.Surface
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import com.android.server.wm.traces.common.service.PlatformConsts
import com.android.wm.shell.flicker.Direction
import org.junit.FixMethodOrder
import org.junit.Test
@@ -36,71 +36,56 @@ import org.junit.runners.Parameterized
* To run this test: `atest WMShellFlickerTests:MovePipUpShelfHeightChangeTest`
*
* Actions:
+ * ```
* Launch [pipApp] in pip mode
* Press home
* Launch [testApp]
* Check if pip window moves down (visually)
- *
+ * ```
* Notes:
+ * ```
* 1. Some default assertions (e.g., nav bar, status bar and screen covered)
* are inherited [PipTransition]
* 2. Part of the test setup occurs automatically via
* [com.android.server.wm.flicker.TransitionRunnerWithRules],
* including configuring navigation mode, initial orientation and ensuring no
* apps are running before setup
+ * ```
*/
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class MovePipDownShelfHeightChangeTest(
- testSpec: FlickerTestParameter
-) : MovePipShelfHeightTransition(testSpec) {
-// @Before
-// fun before() {
-// Assume.assumeFalse(isShellTransitionsEnabled)
-// }
-
- /**
- * Defines the transition used to run the test
- */
+class MovePipDownShelfHeightChangeTest(flicker: FlickerTest) :
+ MovePipShelfHeightTransition(flicker) {
+ /** Defines the transition used to run the test */
override val transition: FlickerBuilder.() -> Unit
get() = buildTransition {
teardown {
tapl.pressHome()
testApp.exit(wmHelper)
}
- transitions {
- testApp.launchViaIntent(wmHelper)
- }
+ transitions { testApp.launchViaIntent(wmHelper) }
}
- /**
- * Checks that the visible region of [pipApp] window always moves down during the animation.
- */
- @Presubmit
- @Test
- fun pipWindowMovesDown() = pipWindowMoves(Direction.DOWN)
+ /** Checks that the visible region of [pipApp] window always moves down during the animation. */
+ @Presubmit @Test fun pipWindowMovesDown() = pipWindowMoves(Direction.DOWN)
- /**
- * Checks that the visible region of [pipApp] layer always moves down during the animation.
- */
- @Presubmit
- @Test
- fun pipLayerMovesDown() = pipLayerMoves(Direction.DOWN)
+ /** Checks that the visible region of [pipApp] layer always moves down during the animation. */
+ @Presubmit @Test fun pipLayerMovesDown() = pipLayerMoves(Direction.DOWN)
companion object {
/**
* Creates the test configurations.
*
- * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
- * repetitions, screen orientation and navigation modes.
+ * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
- supportedRotations = listOf(Surface.ROTATION_0)
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
)
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt
index b40106790b6c..35525cbd4e1a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt
@@ -17,29 +17,28 @@
package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
-import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.helpers.FixedOrientationAppHelper
import com.android.server.wm.flicker.traces.region.RegionSubject
import com.android.wm.shell.flicker.Direction
import org.junit.Test
/** Base class for pip tests with Launcher shelf height change */
-abstract class MovePipShelfHeightTransition(testSpec: FlickerTestParameter) :
- PipTransition(testSpec) {
+abstract class MovePipShelfHeightTransition(flicker: FlickerTest) : PipTransition(flicker) {
protected val testApp = FixedOrientationAppHelper(instrumentation)
/** Checks [pipApp] window remains visible throughout the animation */
@Presubmit
@Test
open fun pipWindowIsAlwaysVisible() {
- testSpec.assertWm { isAppWindowVisible(pipApp) }
+ flicker.assertWm { isAppWindowVisible(pipApp) }
}
/** Checks [pipApp] layer remains visible throughout the animation */
@Presubmit
@Test
open fun pipLayerIsAlwaysVisible() {
- testSpec.assertLayers { isVisible(pipApp) }
+ flicker.assertLayers { isVisible(pipApp) }
}
/**
@@ -49,7 +48,7 @@ abstract class MovePipShelfHeightTransition(testSpec: FlickerTestParameter) :
@Presubmit
@Test
open fun pipWindowRemainInsideVisibleBounds() {
- testSpec.assertWmVisibleRegion(pipApp) { coversAtMost(displayBounds) }
+ flicker.assertWmVisibleRegion(pipApp) { coversAtMost(displayBounds) }
}
/**
@@ -59,7 +58,7 @@ abstract class MovePipShelfHeightTransition(testSpec: FlickerTestParameter) :
@Presubmit
@Test
open fun pipLayerRemainInsideVisibleBounds() {
- testSpec.assertLayersVisibleRegion(pipApp) { coversAtMost(displayBounds) }
+ flicker.assertLayersVisibleRegion(pipApp) { coversAtMost(displayBounds) }
}
/**
@@ -67,7 +66,7 @@ abstract class MovePipShelfHeightTransition(testSpec: FlickerTestParameter) :
* during the animation.
*/
protected fun pipWindowMoves(direction: Direction) {
- testSpec.assertWm {
+ flicker.assertWm {
val pipWindowFrameList =
this.windowStates { pipApp.windowMatchesAnyOf(it) && it.isVisible }.map { it.frame }
when (direction) {
@@ -83,7 +82,7 @@ abstract class MovePipShelfHeightTransition(testSpec: FlickerTestParameter) :
* during the animation.
*/
protected fun pipLayerMoves(direction: Direction) {
- testSpec.assertLayers {
+ flicker.assertLayers {
val pipLayerRegionList =
this.layers { pipApp.layerMatchesAnyOf(it) && it.isVisible }
.map { it.visibleRegion }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt
index 7f8ef3268adb..3a12a34a5206 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt
@@ -17,12 +17,12 @@
package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
-import android.view.Surface
import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import com.android.server.wm.traces.common.service.PlatformConsts
import com.android.wm.shell.flicker.Direction
import org.junit.FixMethodOrder
import org.junit.Test
@@ -36,68 +36,55 @@ import org.junit.runners.Parameterized
* To run this test: `atest WMShellFlickerTests:MovePipDownShelfHeightChangeTest`
*
* Actions:
+ * ```
* Launch [pipApp] in pip mode
* Launch [testApp]
* Press home
* Check if pip window moves up (visually)
- *
+ * ```
* Notes:
+ * ```
* 1. Some default assertions (e.g., nav bar, status bar and screen covered)
* are inherited [PipTransition]
* 2. Part of the test setup occurs automatically via
* [com.android.server.wm.flicker.TransitionRunnerWithRules],
* including configuring navigation mode, initial orientation and ensuring no
* apps are running before setup
+ * ```
*/
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class MovePipUpShelfHeightChangeTest(
- testSpec: FlickerTestParameter
-) : MovePipShelfHeightTransition(testSpec) {
- /**
- * Defines the transition used to run the test
- */
+open class MovePipUpShelfHeightChangeTest(flicker: FlickerTest) :
+ MovePipShelfHeightTransition(flicker) {
+ /** Defines the transition used to run the test */
override val transition: FlickerBuilder.() -> Unit
- get() = buildTransition() {
- setup {
- testApp.launchViaIntent(wmHelper)
- }
- transitions {
- tapl.pressHome()
+ get() =
+ buildTransition() {
+ setup { testApp.launchViaIntent(wmHelper) }
+ transitions { tapl.pressHome() }
+ teardown { testApp.exit(wmHelper) }
}
- teardown {
- testApp.exit(wmHelper)
- }
- }
- /**
- * Checks that the visible region of [pipApp] window always moves up during the animation.
- */
- @Presubmit
- @Test
- fun pipWindowMovesUp() = pipWindowMoves(Direction.UP)
+ /** Checks that the visible region of [pipApp] window always moves up during the animation. */
+ @Presubmit @Test fun pipWindowMovesUp() = pipWindowMoves(Direction.UP)
- /**
- * Checks that the visible region of [pipApp] layer always moves up during the animation.
- */
- @Presubmit
- @Test
- fun pipLayerMovesUp() = pipLayerMoves(Direction.UP)
+ /** Checks that the visible region of [pipApp] layer always moves up during the animation. */
+ @Presubmit @Test fun pipLayerMovesUp() = pipLayerMoves(Direction.UP)
companion object {
/**
* Creates the test configurations.
*
- * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
- * repetitions, screen orientation and navigation modes.
+ * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
- supportedRotations = listOf(Surface.ROTATION_0)
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
)
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
index 3b64d218a73d..12d6362cb060 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
@@ -17,17 +17,17 @@
package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
-import android.view.Surface
import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.helpers.ImeAppHelper
import com.android.server.wm.flicker.helpers.WindowUtils
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.traces.common.ComponentNameMatcher
+import com.android.server.wm.traces.common.service.PlatformConsts
import org.junit.Assume.assumeFalse
import org.junit.Before
import org.junit.FixMethodOrder
@@ -41,7 +41,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class PipKeyboardTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
+open class PipKeyboardTest(flicker: FlickerTest) : PipTransition(flicker) {
private val imeApp = ImeAppHelper(instrumentation)
@Before
@@ -54,11 +54,11 @@ open class PipKeyboardTest(testSpec: FlickerTestParameter) : PipTransition(testS
get() = buildTransition {
setup {
imeApp.launchViaIntent(wmHelper)
- setRotation(testSpec.startRotation)
+ setRotation(flicker.scenario.startRotation)
}
teardown {
imeApp.exit(wmHelper)
- setRotation(Surface.ROTATION_0)
+ setRotation(PlatformConsts.Rotation.ROTATION_0)
}
transitions {
// open the soft keyboard
@@ -74,8 +74,8 @@ open class PipKeyboardTest(testSpec: FlickerTestParameter) : PipTransition(testS
@Presubmit
@Test
open fun pipInVisibleBounds() {
- testSpec.assertWmVisibleRegion(pipApp) {
- val displayBounds = WindowUtils.getDisplayBounds(testSpec.startRotation)
+ flicker.assertWmVisibleRegion(pipApp) {
+ val displayBounds = WindowUtils.getDisplayBounds(flicker.scenario.startRotation)
coversAtMost(displayBounds)
}
}
@@ -84,7 +84,7 @@ open class PipKeyboardTest(testSpec: FlickerTestParameter) : PipTransition(testS
@Presubmit
@Test
open fun pipIsAboveAppWindow() {
- testSpec.assertWmTag(TAG_IME_VISIBLE) { isAboveWindow(ComponentNameMatcher.IME, pipApp) }
+ flicker.assertWmTag(TAG_IME_VISIBLE) { isAboveWindow(ComponentNameMatcher.IME, pipApp) }
}
companion object {
@@ -92,9 +92,10 @@ open class PipKeyboardTest(testSpec: FlickerTestParameter) : PipTransition(testS
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0))
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
+ )
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTestShellTransit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTestShellTransit.kt
index 2a82c00bebd3..901814e21971 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTestShellTransit.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTestShellTransit.kt
@@ -18,9 +18,9 @@ package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import org.junit.Assume
import org.junit.Before
import org.junit.FixMethodOrder
@@ -33,7 +33,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class PipKeyboardTestShellTransit(testSpec: FlickerTestParameter) : PipKeyboardTest(testSpec) {
+class PipKeyboardTestShellTransit(flicker: FlickerTest) : PipKeyboardTest(flicker) {
@Before
override fun before() {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
index 7de5494a7733..eee00bd1e699 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
@@ -18,16 +18,15 @@ package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
-import android.view.Surface
import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.helpers.SimpleAppHelper
import com.android.server.wm.flicker.helpers.WindowUtils
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import org.junit.Assume
import org.junit.Before
import org.junit.FixMethodOrder
@@ -45,7 +44,7 @@ import org.junit.runners.Parameterized
* ```
* Launch a [pipApp] in pip mode
* Launch another app [fixedApp] (appears below pip)
- * Rotate the screen from [testSpec.startRotation] to [testSpec.endRotation]
+ * Rotate the screen from [flicker.scenario.startRotation] to [flicker.scenario.endRotation]
* (usually, 0->90 and 90->0)
* ```
* Notes:
@@ -62,10 +61,10 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class PipRotationTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
+open class PipRotationTest(flicker: FlickerTest) : PipTransition(flicker) {
private val testApp = SimpleAppHelper(instrumentation)
- private val screenBoundsStart = WindowUtils.getDisplayBounds(testSpec.startRotation)
- private val screenBoundsEnd = WindowUtils.getDisplayBounds(testSpec.endRotation)
+ private val screenBoundsStart = WindowUtils.getDisplayBounds(flicker.scenario.startRotation)
+ private val screenBoundsEnd = WindowUtils.getDisplayBounds(flicker.scenario.endRotation)
@Before
open fun before() {
@@ -76,9 +75,9 @@ open class PipRotationTest(testSpec: FlickerTestParameter) : PipTransition(testS
get() = buildTransition {
setup {
testApp.launchViaIntent(wmHelper)
- setRotation(testSpec.startRotation)
+ setRotation(flicker.scenario.startRotation)
}
- transitions { setRotation(testSpec.endRotation) }
+ transitions { setRotation(flicker.scenario.endRotation) }
}
/** Checks the position of the navigation bar at the start and end of the transition */
@@ -90,14 +89,14 @@ open class PipRotationTest(testSpec: FlickerTestParameter) : PipTransition(testS
@Presubmit
@Test
fun fixedAppLayer_StartingBounds() {
- testSpec.assertLayersStart { visibleRegion(testApp).coversAtMost(screenBoundsStart) }
+ flicker.assertLayersStart { visibleRegion(testApp).coversAtMost(screenBoundsStart) }
}
/** Checks that [testApp] layer is within [screenBoundsEnd] at the end of the transition */
@Presubmit
@Test
fun fixedAppLayer_EndingBounds() {
- testSpec.assertLayersEnd { visibleRegion(testApp).coversAtMost(screenBoundsEnd) }
+ flicker.assertLayersEnd { visibleRegion(testApp).coversAtMost(screenBoundsEnd) }
}
/**
@@ -107,7 +106,7 @@ open class PipRotationTest(testSpec: FlickerTestParameter) : PipTransition(testS
@Presubmit
@Test
fun appLayers_StartingBounds() {
- testSpec.assertLayersStart {
+ flicker.assertLayersStart {
visibleRegion(testApp.or(pipApp)).coversExactly(screenBoundsStart)
}
}
@@ -119,14 +118,12 @@ open class PipRotationTest(testSpec: FlickerTestParameter) : PipTransition(testS
@Presubmit
@Test
fun appLayers_EndingBounds() {
- testSpec.assertLayersEnd {
- visibleRegion(testApp.or(pipApp)).coversExactly(screenBoundsEnd)
- }
+ flicker.assertLayersEnd { visibleRegion(testApp.or(pipApp)).coversExactly(screenBoundsEnd) }
}
/** Checks that [pipApp] layer is within [screenBoundsStart] at the start of the transition */
private fun pipLayerRotates_StartingBounds_internal() {
- testSpec.assertLayersStart { visibleRegion(pipApp).coversAtMost(screenBoundsStart) }
+ flicker.assertLayersStart { visibleRegion(pipApp).coversAtMost(screenBoundsStart) }
}
/** Checks that [pipApp] layer is within [screenBoundsStart] at the start of the transition */
@@ -140,7 +137,7 @@ open class PipRotationTest(testSpec: FlickerTestParameter) : PipTransition(testS
@Presubmit
@Test
fun pipLayerRotates_EndingBounds() {
- testSpec.assertLayersEnd { visibleRegion(pipApp).coversAtMost(screenBoundsEnd) }
+ flicker.assertLayersEnd { visibleRegion(pipApp).coversAtMost(screenBoundsEnd) }
}
/**
@@ -149,7 +146,7 @@ open class PipRotationTest(testSpec: FlickerTestParameter) : PipTransition(testS
@Presubmit
@Test
fun pipIsAboveFixedAppWindow_Start() {
- testSpec.assertWmStart { isAboveWindow(pipApp, testApp) }
+ flicker.assertWmStart { isAboveWindow(pipApp, testApp) }
}
/**
@@ -158,7 +155,7 @@ open class PipRotationTest(testSpec: FlickerTestParameter) : PipTransition(testS
@Presubmit
@Test
fun pipIsAboveFixedAppWindow_End() {
- testSpec.assertWmEnd { isAboveWindow(pipApp, testApp) }
+ flicker.assertWmEnd { isAboveWindow(pipApp, testApp) }
}
@Presubmit
@@ -171,16 +168,13 @@ open class PipRotationTest(testSpec: FlickerTestParameter) : PipTransition(testS
/**
* Creates the test configurations.
*
- * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
- * screen orientation and navigation modes.
+ * See [FlickerTestFactory.nonRotationTests] for configuring repetitions, screen orientation
+ * and navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance()
- .getConfigRotationTests(
- supportedRotations = listOf(Surface.ROTATION_0, Surface.ROTATION_90)
- )
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.rotationTests()
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest_ShellTransit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest_ShellTransit.kt
index 983cb1c6bafd..d0d9167555eb 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest_ShellTransit.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest_ShellTransit.kt
@@ -18,9 +18,9 @@ package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.FlakyTest
import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import org.junit.Assume
import org.junit.Before
import org.junit.FixMethodOrder
@@ -38,7 +38,7 @@ import org.junit.runners.Parameterized
* ```
* Launch a [pipApp] in pip mode
* Launch another app [fixedApp] (appears below pip)
- * Rotate the screen from [testSpec.startRotation] to [testSpec.endRotation]
+ * Rotate the screen from [flicker.scenario.startRotation] to [flicker.scenario.endRotation]
* (usually, 0->90 and 90->0)
* ```
* Notes:
@@ -56,7 +56,7 @@ import org.junit.runners.Parameterized
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@FlakyTest(bugId = 239575053)
-class PipRotationTest_ShellTransit(testSpec: FlickerTestParameter) : PipRotationTest(testSpec) {
+class PipRotationTest_ShellTransit(flicker: FlickerTest) : PipRotationTest(flicker) {
@Before
override fun before() {
Assume.assumeTrue(isShellTransitionsEnabled)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt
index dfa25104ccc8..0e0be79e0aa0 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt
@@ -18,19 +18,19 @@ package com.android.wm.shell.flicker.pip
import android.app.Instrumentation
import android.content.Intent
-import android.view.Surface
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.helpers.PipAppHelper
import com.android.server.wm.flicker.helpers.WindowUtils
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome
import com.android.server.wm.flicker.testapp.ActivityOptions
+import com.android.server.wm.traces.common.service.PlatformConsts
import com.android.wm.shell.flicker.BaseTest
-abstract class PipTransition(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
+abstract class PipTransition(flicker: FlickerTest) : BaseTest(flicker) {
protected val pipApp = PipAppHelper(instrumentation)
- protected val displayBounds = WindowUtils.getDisplayBounds(testSpec.startRotation)
+ protected val displayBounds = WindowUtils.getDisplayBounds(flicker.scenario.startRotation)
protected val broadcastActionTrigger = BroadcastActionTrigger(instrumentation)
// Helper class to process test actions by broadcast.
@@ -67,12 +67,12 @@ abstract class PipTransition(testSpec: FlickerTestParameter) : BaseTest(testSpec
): FlickerBuilder.() -> Unit {
return {
setup {
- setRotation(Surface.ROTATION_0)
+ setRotation(PlatformConsts.Rotation.ROTATION_0)
removeAllTasksButHome()
pipApp.launchViaIntentAndWaitForPip(wmHelper, stringExtras = stringExtras)
}
teardown {
- setRotation(Surface.ROTATION_0)
+ setRotation(PlatformConsts.Rotation.ROTATION_0)
removeAllTasksButHome()
pipApp.exit(wmHelper)
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
index f0093e6c03b5..157aa9863fdf 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
@@ -20,19 +20,19 @@ import android.app.Activity
import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
-import android.view.Surface
import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.helpers.WindowUtils
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome
import com.android.server.wm.flicker.testapp.ActivityOptions
import com.android.server.wm.flicker.testapp.ActivityOptions.PortraitOnlyActivity.EXTRA_FIXED_ORIENTATION
+import com.android.server.wm.traces.common.service.PlatformConsts
import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_LANDSCAPE
import org.junit.Assume
import org.junit.Before
@@ -43,20 +43,18 @@ import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
/**
- * Test exiting Pip with orientation changes.
- * To run this test: `atest WMShellFlickerTests:SetRequestedOrientationWhilePinnedTest`
+ * Test exiting Pip with orientation changes. To run this test: `atest
+ * WMShellFlickerTests:SetRequestedOrientationWhilePinnedTest`
*/
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class SetRequestedOrientationWhilePinnedTest(
- testSpec: FlickerTestParameter
-) : PipTransition(testSpec) {
- private val startingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_0)
- private val endingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_90)
+open class SetRequestedOrientationWhilePinnedTest(flicker: FlickerTest) : PipTransition(flicker) {
+ private val startingBounds = WindowUtils.getDisplayBounds(PlatformConsts.Rotation.ROTATION_0)
+ private val endingBounds = WindowUtils.getDisplayBounds(PlatformConsts.Rotation.ROTATION_90)
- /** {@inheritDoc} */
+ /** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit
get() = {
setup {
@@ -64,30 +62,35 @@ open class SetRequestedOrientationWhilePinnedTest(
device.wakeUpAndGoToHomeScreen()
// Launch the PiP activity fixed as landscape.
- pipApp.launchViaIntent(wmHelper, stringExtras = mapOf(
- EXTRA_FIXED_ORIENTATION to ORIENTATION_LANDSCAPE.toString()))
+ pipApp.launchViaIntent(
+ wmHelper,
+ stringExtras =
+ mapOf(EXTRA_FIXED_ORIENTATION to ORIENTATION_LANDSCAPE.toString())
+ )
// Enter PiP.
broadcastActionTrigger.doAction(ActivityOptions.Pip.ACTION_ENTER_PIP)
// System bar may fade out during fixed rotation.
- wmHelper.StateSyncBuilder()
+ wmHelper
+ .StateSyncBuilder()
.withPipShown()
- .withRotation(Surface.ROTATION_0)
+ .withRotation(PlatformConsts.Rotation.ROTATION_0)
.withNavOrTaskBarVisible()
.withStatusBarVisible()
.waitForAndVerify()
}
teardown {
pipApp.exit(wmHelper)
- setRotation(Surface.ROTATION_0)
+ setRotation(PlatformConsts.Rotation.ROTATION_0)
removeAllTasksButHome()
}
transitions {
// Launch the activity back into fullscreen and ensure that it is now in landscape
pipApp.launchViaIntent(wmHelper)
// System bar may fade out during fixed rotation.
- wmHelper.StateSyncBuilder()
+ wmHelper
+ .StateSyncBuilder()
.withFullScreenApp(pipApp)
- .withRotation(Surface.ROTATION_90)
+ .withRotation(PlatformConsts.Rotation.ROTATION_90)
.withNavOrTaskBarVisible()
.withStatusBarVisible()
.waitForAndVerify()
@@ -95,34 +98,32 @@ open class SetRequestedOrientationWhilePinnedTest(
}
/**
- * This test is not compatible with Tablets. When using [Activity.setRequestedOrientation]
- * to fix a orientation, Tablets instead keep the same orientation and add letterboxes
+ * This test is not compatible with Tablets. When using [Activity.setRequestedOrientation] to
+ * fix a orientation, Tablets instead keep the same orientation and add letterboxes
*/
@Before
fun setup() {
- Assume.assumeFalse(testSpec.isTablet)
+ Assume.assumeFalse(flicker.scenario.isTablet)
}
@Presubmit
@Test
fun displayEndsAt90Degrees() {
- testSpec.assertWmEnd {
- hasRotation(Surface.ROTATION_90)
- }
+ flicker.assertWmEnd { hasRotation(PlatformConsts.Rotation.ROTATION_90) }
}
- /** {@inheritDoc} */
+ /** {@inheritDoc} */
@Presubmit
@Test
override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
- /** {@inheritDoc} */
+ /** {@inheritDoc} */
@Presubmit
@Test
override fun statusBarLayerIsVisibleAtStartAndEnd() =
super.statusBarLayerIsVisibleAtStartAndEnd()
- /** {@inheritDoc} */
+ /** {@inheritDoc} */
@FlakyTest
@Test
override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
@@ -130,23 +131,17 @@ open class SetRequestedOrientationWhilePinnedTest(
@Presubmit
@Test
fun pipWindowInsideDisplay() {
- testSpec.assertWmStart {
- visibleRegion(pipApp).coversAtMost(startingBounds)
- }
+ flicker.assertWmStart { visibleRegion(pipApp).coversAtMost(startingBounds) }
}
@Presubmit
@Test
fun pipAppShowsOnTop() {
- testSpec.assertWmEnd {
- isAppWindowOnTop(pipApp)
- }
+ flicker.assertWmEnd { isAppWindowOnTop(pipApp) }
}
private fun pipLayerInsideDisplay_internal() {
- testSpec.assertLayersStart {
- visibleRegion(pipApp).coversAtMost(startingBounds)
- }
+ flicker.assertLayersStart { visibleRegion(pipApp).coversAtMost(startingBounds) }
}
@Presubmit
@@ -166,40 +161,35 @@ open class SetRequestedOrientationWhilePinnedTest(
@Presubmit
@Test
fun pipAlwaysVisible() {
- testSpec.assertWm {
- this.isAppWindowVisible(pipApp)
- }
+ flicker.assertWm { this.isAppWindowVisible(pipApp) }
}
@Presubmit
@Test
fun pipAppLayerCoversFullScreen() {
- testSpec.assertLayersEnd {
- visibleRegion(pipApp).coversExactly(endingBounds)
- }
+ flicker.assertLayersEnd { visibleRegion(pipApp).coversExactly(endingBounds) }
}
- /** {@inheritDoc} */
+ /** {@inheritDoc} */
@Postsubmit
@Test
override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
- /** {@inheritDoc} */
+ /** {@inheritDoc} */
@Postsubmit
@Test
override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible()
- /** {@inheritDoc} */
- @Postsubmit
- @Test
- override fun entireScreenCovered() = super.entireScreenCovered()
+ /** {@inheritDoc} */
+ @Postsubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0))
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
+ )
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/PipTestBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/PipTestBase.kt
index 2cb18f948f0e..a16f5f6f1620 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/PipTestBase.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/PipTestBase.kt
@@ -24,7 +24,10 @@ import androidx.test.uiautomator.UiDevice
import org.junit.Before
import org.junit.runners.Parameterized
-abstract class PipTestBase(protected val rotationName: String, protected val rotation: Int) {
+abstract class PipTestBase(
+ protected val rotationName: String,
+ protected val rotation: Int
+) {
val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
val uiDevice = UiDevice.getInstance(instrumentation)
val packageManager: PackageManager = instrumentation.context.packageManager
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
index 9533b9182b6c..65cbea03a044 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
@@ -20,10 +20,10 @@ import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.IwTest
import android.platform.test.annotations.Presubmit
import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.traces.common.ComponentNameMatcher
import com.android.server.wm.traces.common.EdgeExtensionComponentMatcher
import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
@@ -49,7 +49,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class CopyContentInSplit(testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) {
+class CopyContentInSplit(flicker: FlickerTest) : SplitScreenBase(flicker) {
private val textEditApp = SplitScreenUtils.getIme(instrumentation)
private val MagnifierLayer = ComponentNameMatcher("", "magnifier surface bbq wrapper#")
private val PopupWindowLayer = ComponentNameMatcher("", "PopupWindow:")
@@ -72,29 +72,29 @@ class CopyContentInSplit(testSpec: FlickerTestParameter) : SplitScreenBase(testS
@Presubmit
@Test
fun cujCompleted() {
- testSpec.appWindowIsVisibleAtStart(primaryApp)
- testSpec.appWindowIsVisibleAtStart(textEditApp)
- testSpec.splitScreenDividerIsVisibleAtStart()
+ flicker.appWindowIsVisibleAtStart(primaryApp)
+ flicker.appWindowIsVisibleAtStart(textEditApp)
+ flicker.splitScreenDividerIsVisibleAtStart()
- testSpec.appWindowIsVisibleAtEnd(primaryApp)
- testSpec.appWindowIsVisibleAtEnd(textEditApp)
- testSpec.splitScreenDividerIsVisibleAtEnd()
+ flicker.appWindowIsVisibleAtEnd(primaryApp)
+ flicker.appWindowIsVisibleAtEnd(textEditApp)
+ flicker.splitScreenDividerIsVisibleAtEnd()
// The validation of copied text is already done in SplitScreenUtils.copyContentInSplit()
}
@Presubmit
@Test
- fun splitScreenDividerKeepVisible() = testSpec.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
+ fun splitScreenDividerKeepVisible() = flicker.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
- @Presubmit @Test fun primaryAppLayerKeepVisible() = testSpec.layerKeepVisible(primaryApp)
+ @Presubmit @Test fun primaryAppLayerKeepVisible() = flicker.layerKeepVisible(primaryApp)
- @Presubmit @Test fun textEditAppLayerKeepVisible() = testSpec.layerKeepVisible(textEditApp)
+ @Presubmit @Test fun textEditAppLayerKeepVisible() = flicker.layerKeepVisible(textEditApp)
@Presubmit
@Test
fun primaryAppBoundsKeepVisible() =
- testSpec.splitAppLayerBoundsKeepVisible(
+ flicker.splitAppLayerBoundsKeepVisible(
primaryApp,
landscapePosLeft = tapl.isTablet,
portraitPosTop = false
@@ -103,21 +103,18 @@ class CopyContentInSplit(testSpec: FlickerTestParameter) : SplitScreenBase(testS
@Presubmit
@Test
fun textEditAppBoundsKeepVisible() =
- testSpec.splitAppLayerBoundsKeepVisible(
+ flicker.splitAppLayerBoundsKeepVisible(
textEditApp,
landscapePosLeft = !tapl.isTablet,
portraitPosTop = true
)
- @Presubmit @Test fun primaryAppWindowKeepVisible() = testSpec.appWindowKeepVisible(primaryApp)
+ @Presubmit @Test fun primaryAppWindowKeepVisible() = flicker.appWindowKeepVisible(primaryApp)
- @Presubmit @Test fun textEditAppWindowKeepVisible() = testSpec.appWindowKeepVisible(textEditApp)
+ @Presubmit @Test fun textEditAppWindowKeepVisible() = flicker.appWindowKeepVisible(textEditApp)
/** {@inheritDoc} */
- @Presubmit
- @Test
- override fun entireScreenCovered() =
- super.entireScreenCovered()
+ @Presubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
/** {@inheritDoc} */
@Presubmit
@@ -164,15 +161,18 @@ class CopyContentInSplit(testSpec: FlickerTestParameter) : SplitScreenBase(testS
@Presubmit
@Test
override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
- testSpec.assertLayers {
+ flicker.assertLayers {
this.visibleLayersShownMoreThanOneConsecutiveEntry(
- ignoreLayers = listOf(
- ComponentNameMatcher.SPLASH_SCREEN,
- ComponentNameMatcher.SNAPSHOT,
- ComponentNameMatcher.IME_SNAPSHOT,
- EdgeExtensionComponentMatcher(),
- MagnifierLayer,
- PopupWindowLayer))
+ ignoreLayers =
+ listOf(
+ ComponentNameMatcher.SPLASH_SCREEN,
+ ComponentNameMatcher.SNAPSHOT,
+ ComponentNameMatcher.IME_SNAPSHOT,
+ EdgeExtensionComponentMatcher(),
+ MagnifierLayer,
+ PopupWindowLayer
+ )
+ )
}
}
@@ -185,9 +185,8 @@ class CopyContentInSplit(testSpec: FlickerTestParameter) : SplitScreenBase(testS
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests()
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests()
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
index 475749834711..d0f02e2bf514 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
@@ -21,11 +21,11 @@ import android.platform.test.annotations.IwTest
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.helpers.WindowUtils
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
import com.android.wm.shell.flicker.appWindowBecomesInvisible
import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
@@ -49,54 +49,62 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class DismissSplitScreenByDivider (testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) {
+class DismissSplitScreenByDivider(flicker: FlickerTest) : SplitScreenBase(flicker) {
override val transition: FlickerBuilder.() -> Unit
get() = {
super.transition(this)
- setup {
- SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
- }
+ setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) }
transitions {
if (tapl.isTablet) {
- SplitScreenUtils.dragDividerToDismissSplit(device, wmHelper,
- dragToRight = false, dragToBottom = true)
+ SplitScreenUtils.dragDividerToDismissSplit(
+ device,
+ wmHelper,
+ dragToRight = false,
+ dragToBottom = true
+ )
} else {
- SplitScreenUtils.dragDividerToDismissSplit(device, wmHelper,
- dragToRight = true, dragToBottom = true)
+ SplitScreenUtils.dragDividerToDismissSplit(
+ device,
+ wmHelper,
+ dragToRight = true,
+ dragToBottom = true
+ )
}
- wmHelper.StateSyncBuilder()
- .withFullScreenApp(secondaryApp)
- .waitForAndVerify()
+ wmHelper.StateSyncBuilder().withFullScreenApp(secondaryApp).waitForAndVerify()
}
}
@IwTest(focusArea = "sysui")
@Presubmit
@Test
- fun cujCompleted() = testSpec.splitScreenDismissed(primaryApp, secondaryApp, toHome = false)
+ fun cujCompleted() = flicker.splitScreenDismissed(primaryApp, secondaryApp, toHome = false)
@Presubmit
@Test
- fun splitScreenDividerBecomesInvisible() = testSpec.splitScreenDividerBecomesInvisible()
+ fun splitScreenDividerBecomesInvisible() = flicker.splitScreenDividerBecomesInvisible()
@Presubmit
@Test
- fun primaryAppLayerBecomesInvisible() = testSpec.layerBecomesInvisible(primaryApp)
+ fun primaryAppLayerBecomesInvisible() = flicker.layerBecomesInvisible(primaryApp)
@Presubmit
@Test
- fun secondaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(secondaryApp)
+ fun secondaryAppLayerIsVisibleAtEnd() = flicker.layerIsVisibleAtEnd(secondaryApp)
@Presubmit
@Test
- fun primaryAppBoundsBecomesInvisible() = testSpec.splitAppLayerBoundsBecomesInvisible(
- primaryApp, landscapePosLeft = tapl.isTablet, portraitPosTop = false)
+ fun primaryAppBoundsBecomesInvisible() =
+ flicker.splitAppLayerBoundsBecomesInvisible(
+ primaryApp,
+ landscapePosLeft = tapl.isTablet,
+ portraitPosTop = false
+ )
@Presubmit
@Test
fun secondaryAppBoundsIsFullscreenAtEnd() {
- testSpec.assertLayers {
+ flicker.assertLayers {
this.isVisible(secondaryApp)
.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
.then()
@@ -109,7 +117,7 @@ class DismissSplitScreenByDivider (testSpec: FlickerTestParameter) : SplitScreen
.contains(SPLIT_SCREEN_DIVIDER_COMPONENT)
.then()
.invoke("secondaryAppBoundsIsFullscreenAtEnd") {
- val displayBounds = WindowUtils.getDisplayBounds(testSpec.endRotation)
+ val displayBounds = WindowUtils.getDisplayBounds(flicker.scenario.endRotation)
it.visibleRegion(secondaryApp).coversExactly(displayBounds)
}
}
@@ -117,35 +125,29 @@ class DismissSplitScreenByDivider (testSpec: FlickerTestParameter) : SplitScreen
@Presubmit
@Test
- fun primaryAppWindowBecomesInvisible() = testSpec.appWindowBecomesInvisible(primaryApp)
+ fun primaryAppWindowBecomesInvisible() = flicker.appWindowBecomesInvisible(primaryApp)
@Presubmit
@Test
- fun secondaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(secondaryApp)
+ fun secondaryAppWindowIsVisibleAtEnd() = flicker.appWindowIsVisibleAtEnd(secondaryApp)
/** {@inheritDoc} */
- @Postsubmit
- @Test
- override fun entireScreenCovered() =
- super.entireScreenCovered()
+ @Postsubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun navBarLayerIsVisibleAtStartAndEnd() =
- super.navBarLayerIsVisibleAtStartAndEnd()
+ override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
/** {@inheritDoc} */
@FlakyTest(bugId = 206753786)
@Test
- override fun navBarLayerPositionAtStartAndEnd() =
- super.navBarLayerPositionAtStartAndEnd()
+ override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun navBarWindowIsAlwaysVisible() =
- super.navBarWindowIsAlwaysVisible()
+ override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
/** {@inheritDoc} */
@Postsubmit
@@ -156,26 +158,22 @@ class DismissSplitScreenByDivider (testSpec: FlickerTestParameter) : SplitScreen
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun statusBarLayerPositionAtStartAndEnd() =
- super.statusBarLayerPositionAtStartAndEnd()
+ override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun statusBarWindowIsAlwaysVisible() =
- super.statusBarWindowIsAlwaysVisible()
+ override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun taskBarLayerIsVisibleAtStartAndEnd() =
- super.taskBarLayerIsVisibleAtStartAndEnd()
+ override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun taskBarWindowIsAlwaysVisible() =
- super.taskBarWindowIsAlwaysVisible()
+ override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible()
/** {@inheritDoc} */
@Postsubmit
@@ -192,8 +190,8 @@ class DismissSplitScreenByDivider (testSpec: FlickerTestParameter) : SplitScreen
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests()
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests()
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
index 1d61955bc0a8..b44b681704ba 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
@@ -20,10 +20,10 @@ import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.IwTest
import android.platform.test.annotations.Presubmit
import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.wm.shell.flicker.appWindowBecomesInvisible
import com.android.wm.shell.flicker.layerBecomesInvisible
import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesInvisible
@@ -44,89 +44,81 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class DismissSplitScreenByGoHome(
- testSpec: FlickerTestParameter
-) : SplitScreenBase(testSpec) {
+class DismissSplitScreenByGoHome(flicker: FlickerTest) : SplitScreenBase(flicker) {
override val transition: FlickerBuilder.() -> Unit
get() = {
super.transition(this)
- setup {
- SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
- }
+ setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) }
transitions {
tapl.goHome()
- wmHelper.StateSyncBuilder()
- .withHomeActivityVisible()
- .waitForAndVerify()
+ wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
}
}
@IwTest(focusArea = "sysui")
@Presubmit
@Test
- fun cujCompleted() = testSpec.splitScreenDismissed(primaryApp, secondaryApp, toHome = true)
+ fun cujCompleted() = flicker.splitScreenDismissed(primaryApp, secondaryApp, toHome = true)
@Presubmit
@Test
- fun splitScreenDividerBecomesInvisible() = testSpec.splitScreenDividerBecomesInvisible()
+ fun splitScreenDividerBecomesInvisible() = flicker.splitScreenDividerBecomesInvisible()
@FlakyTest(bugId = 241525302)
@Test
- fun primaryAppLayerBecomesInvisible() = testSpec.layerBecomesInvisible(primaryApp)
+ fun primaryAppLayerBecomesInvisible() = flicker.layerBecomesInvisible(primaryApp)
// TODO(b/245472831): Move back to presubmit after shell transitions landing.
@FlakyTest(bugId = 245472831)
@Test
- fun secondaryAppLayerBecomesInvisible() = testSpec.layerBecomesInvisible(primaryApp)
+ fun secondaryAppLayerBecomesInvisible() = flicker.layerBecomesInvisible(primaryApp)
// TODO(b/245472831): Move back to presubmit after shell transitions landing.
@FlakyTest(bugId = 245472831)
@Test
- fun primaryAppBoundsBecomesInvisible() = testSpec.splitAppLayerBoundsBecomesInvisible(
- primaryApp,
- landscapePosLeft = tapl.isTablet,
- portraitPosTop = false
- )
+ fun primaryAppBoundsBecomesInvisible() =
+ flicker.splitAppLayerBoundsBecomesInvisible(
+ primaryApp,
+ landscapePosLeft = tapl.isTablet,
+ portraitPosTop = false
+ )
@FlakyTest(bugId = 250530241)
@Test
- fun secondaryAppBoundsBecomesInvisible() = testSpec.splitAppLayerBoundsBecomesInvisible(
- secondaryApp,
- landscapePosLeft = !tapl.isTablet,
- portraitPosTop = true
- )
+ fun secondaryAppBoundsBecomesInvisible() =
+ flicker.splitAppLayerBoundsBecomesInvisible(
+ secondaryApp,
+ landscapePosLeft = !tapl.isTablet,
+ portraitPosTop = true
+ )
@Presubmit
@Test
- fun primaryAppWindowBecomesInvisible() = testSpec.appWindowBecomesInvisible(primaryApp)
+ fun primaryAppWindowBecomesInvisible() = flicker.appWindowBecomesInvisible(primaryApp)
@Presubmit
@Test
- fun secondaryAppWindowBecomesInvisible() = testSpec.appWindowBecomesInvisible(secondaryApp)
+ fun secondaryAppWindowBecomesInvisible() = flicker.appWindowBecomesInvisible(secondaryApp)
/** {@inheritDoc} */
@FlakyTest(bugId = 251268711)
@Test
- override fun entireScreenCovered() =
- super.entireScreenCovered()
+ override fun entireScreenCovered() = super.entireScreenCovered()
/** {@inheritDoc} */
@Presubmit
@Test
- override fun navBarLayerIsVisibleAtStartAndEnd() =
- super.navBarLayerIsVisibleAtStartAndEnd()
+ override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
/** {@inheritDoc} */
@FlakyTest(bugId = 206753786)
@Test
- override fun navBarLayerPositionAtStartAndEnd() =
- super.navBarLayerPositionAtStartAndEnd()
+ override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
/** {@inheritDoc} */
@Presubmit
@Test
- override fun navBarWindowIsAlwaysVisible() =
- super.navBarWindowIsAlwaysVisible()
+ override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
/** {@inheritDoc} */
@Presubmit
@@ -137,26 +129,22 @@ class DismissSplitScreenByGoHome(
/** {@inheritDoc} */
@Presubmit
@Test
- override fun statusBarLayerPositionAtStartAndEnd() =
- super.statusBarLayerPositionAtStartAndEnd()
+ override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd()
/** {@inheritDoc} */
@Presubmit
@Test
- override fun statusBarWindowIsAlwaysVisible() =
- super.statusBarWindowIsAlwaysVisible()
+ override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible()
/** {@inheritDoc} */
@Presubmit
@Test
- override fun taskBarLayerIsVisibleAtStartAndEnd() =
- super.taskBarLayerIsVisibleAtStartAndEnd()
+ override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
/** {@inheritDoc} */
@Presubmit
@Test
- override fun taskBarWindowIsAlwaysVisible() =
- super.taskBarWindowIsAlwaysVisible()
+ override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible()
/** {@inheritDoc} */
@FlakyTest
@@ -173,8 +161,8 @@ class DismissSplitScreenByGoHome(
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests()
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests()
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
index 8d771fe3a1ff..5b656b3a5f39 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
@@ -21,10 +21,10 @@ import android.platform.test.annotations.IwTest
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
import com.android.wm.shell.flicker.appWindowIsVisibleAtStart
@@ -50,35 +50,31 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class DragDividerToResize(testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) {
+class DragDividerToResize(flicker: FlickerTest) : SplitScreenBase(flicker) {
override val transition: FlickerBuilder.() -> Unit
get() = {
super.transition(this)
- setup {
- SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
- }
- transitions {
- SplitScreenUtils.dragDividerToResizeAndWait(device, wmHelper)
- }
+ setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) }
+ transitions { SplitScreenUtils.dragDividerToResizeAndWait(device, wmHelper) }
}
@Before
fun before() {
- Assume.assumeTrue(tapl.isTablet || !testSpec.isLandscapeOrSeascapeAtStart)
+ Assume.assumeTrue(tapl.isTablet || !flicker.scenario.isLandscapeOrSeascapeAtStart)
}
@IwTest(focusArea = "sysui")
@Presubmit
@Test
fun cujCompleted() {
- testSpec.appWindowIsVisibleAtStart(primaryApp)
- testSpec.appWindowIsVisibleAtStart(secondaryApp)
- testSpec.splitScreenDividerIsVisibleAtStart()
+ flicker.appWindowIsVisibleAtStart(primaryApp)
+ flicker.appWindowIsVisibleAtStart(secondaryApp)
+ flicker.splitScreenDividerIsVisibleAtStart()
- testSpec.appWindowIsVisibleAtEnd(primaryApp)
- testSpec.appWindowIsVisibleAtEnd(secondaryApp)
- testSpec.splitScreenDividerIsVisibleAtEnd()
+ flicker.appWindowIsVisibleAtEnd(primaryApp)
+ flicker.appWindowIsVisibleAtEnd(secondaryApp)
+ flicker.splitScreenDividerIsVisibleAtEnd()
// TODO(b/246490534): Add validation for resized app after withAppTransitionIdle is
// robust enough to get the correct end state.
@@ -86,16 +82,14 @@ class DragDividerToResize(testSpec: FlickerTestParameter) : SplitScreenBase(test
@Presubmit
@Test
- fun splitScreenDividerKeepVisible() = testSpec.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
+ fun splitScreenDividerKeepVisible() = flicker.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
- @Presubmit
- @Test
- fun primaryAppLayerKeepVisible() = testSpec.layerKeepVisible(primaryApp)
+ @Presubmit @Test fun primaryAppLayerKeepVisible() = flicker.layerKeepVisible(primaryApp)
@Presubmit
@Test
fun secondaryAppLayerVisibilityChanges() {
- testSpec.assertLayers {
+ flicker.assertLayers {
this.isVisible(secondaryApp)
.then()
.isInvisible(secondaryApp)
@@ -104,53 +98,47 @@ class DragDividerToResize(testSpec: FlickerTestParameter) : SplitScreenBase(test
}
}
- @Presubmit
- @Test
- fun primaryAppWindowKeepVisible() = testSpec.appWindowKeepVisible(primaryApp)
+ @Presubmit @Test fun primaryAppWindowKeepVisible() = flicker.appWindowKeepVisible(primaryApp)
@Presubmit
@Test
- fun secondaryAppWindowKeepVisible() = testSpec.appWindowKeepVisible(secondaryApp)
+ fun secondaryAppWindowKeepVisible() = flicker.appWindowKeepVisible(secondaryApp)
@Presubmit
@Test
- fun primaryAppBoundsChanges() = testSpec.splitAppLayerBoundsChanges(
- primaryApp,
- landscapePosLeft = true,
- portraitPosTop = false
- )
+ fun primaryAppBoundsChanges() =
+ flicker.splitAppLayerBoundsChanges(
+ primaryApp,
+ landscapePosLeft = true,
+ portraitPosTop = false
+ )
@FlakyTest(bugId = 250530664)
@Test
- fun secondaryAppBoundsChanges() = testSpec.splitAppLayerBoundsChanges(
- secondaryApp,
- landscapePosLeft = false,
- portraitPosTop = true
- )
+ fun secondaryAppBoundsChanges() =
+ flicker.splitAppLayerBoundsChanges(
+ secondaryApp,
+ landscapePosLeft = false,
+ portraitPosTop = true
+ )
/** {@inheritDoc} */
- @Postsubmit
- @Test
- override fun entireScreenCovered() =
- super.entireScreenCovered()
+ @Postsubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun navBarLayerIsVisibleAtStartAndEnd() =
- super.navBarLayerIsVisibleAtStartAndEnd()
+ override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun navBarLayerPositionAtStartAndEnd() =
- super.navBarLayerPositionAtStartAndEnd()
+ override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun navBarWindowIsAlwaysVisible() =
- super.navBarWindowIsAlwaysVisible()
+ override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
/** {@inheritDoc} */
@Postsubmit
@@ -161,26 +149,22 @@ class DragDividerToResize(testSpec: FlickerTestParameter) : SplitScreenBase(test
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun statusBarLayerPositionAtStartAndEnd() =
- super.statusBarLayerPositionAtStartAndEnd()
+ override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun statusBarWindowIsAlwaysVisible() =
- super.statusBarWindowIsAlwaysVisible()
+ override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun taskBarLayerIsVisibleAtStartAndEnd() =
- super.taskBarLayerIsVisibleAtStartAndEnd()
+ override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun taskBarWindowIsAlwaysVisible() =
- super.taskBarWindowIsAlwaysVisible()
+ override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible()
/** {@inheritDoc} */
@Postsubmit
@@ -197,8 +181,8 @@ class DragDividerToResize(testSpec: FlickerTestParameter) : SplitScreenBase(test
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests()
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests()
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
index 7378e213234c..4e36c367f226 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
@@ -19,13 +19,13 @@ package com.android.wm.shell.flicker.splitscreen
import android.platform.test.annotations.IwTest
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
-import android.view.WindowManagerPolicyConstants
import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import com.android.server.wm.traces.common.service.PlatformConsts
import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
import com.android.wm.shell.flicker.appWindowBecomesVisible
import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
@@ -44,8 +44,8 @@ import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
/**
- * Test enter split screen by dragging app icon from all apps.
- * This test is only for large screen devices.
+ * Test enter split screen by dragging app icon from all apps. This test is only for large screen
+ * devices.
*
* To run this test: `atest WMShellFlickerTests:EnterSplitScreenByDragFromAllApps`
*/
@@ -53,9 +53,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class EnterSplitScreenByDragFromAllApps(
- testSpec: FlickerTestParameter
-) : SplitScreenBase(testSpec) {
+class EnterSplitScreenByDragFromAllApps(flicker: FlickerTest) : SplitScreenBase(flicker) {
@Before
fun before() {
@@ -71,9 +69,9 @@ class EnterSplitScreenByDragFromAllApps(
}
transitions {
tapl.launchedAppState.taskbar
- .openAllApps()
- .getAppIcon(secondaryApp.appName)
- .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
+ .openAllApps()
+ .getAppIcon(secondaryApp.appName)
+ .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
}
}
@@ -81,13 +79,13 @@ class EnterSplitScreenByDragFromAllApps(
@IwTest(focusArea = "sysui")
@Presubmit
@Test
- fun cujCompleted() = testSpec.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = false)
+ fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = false)
@Presubmit
@Test
fun splitScreenDividerBecomesVisible() {
Assume.assumeFalse(isShellTransitionsEnabled)
- testSpec.splitScreenDividerBecomesVisible()
+ flicker.splitScreenDividerBecomesVisible()
}
// TODO(b/245472831): Back to splitScreenDividerBecomesVisible after shell transition ready.
@@ -95,60 +93,54 @@ class EnterSplitScreenByDragFromAllApps(
@Test
fun splitScreenDividerIsVisibleAtEnd_ShellTransit() {
Assume.assumeTrue(isShellTransitionsEnabled)
- testSpec.assertLayersEnd {
- this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
- }
+ flicker.assertLayersEnd { this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) }
}
- @Presubmit
- @Test
- fun primaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(primaryApp)
+ @Presubmit @Test fun primaryAppLayerIsVisibleAtEnd() = flicker.layerIsVisibleAtEnd(primaryApp)
@Presubmit
@Test
- fun secondaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(secondaryApp)
+ fun secondaryAppLayerBecomesVisible() = flicker.layerBecomesVisible(secondaryApp)
@Presubmit
@Test
- fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
- primaryApp, landscapePosLeft = false, portraitPosTop = false)
+ fun primaryAppBoundsIsVisibleAtEnd() =
+ flicker.splitAppLayerBoundsIsVisibleAtEnd(
+ primaryApp,
+ landscapePosLeft = false,
+ portraitPosTop = false
+ )
@Presubmit
@Test
- fun secondaryAppBoundsBecomesVisible() = testSpec.splitAppLayerBoundsBecomesVisibleByDrag(
- secondaryApp)
+ fun secondaryAppBoundsBecomesVisible() =
+ flicker.splitAppLayerBoundsBecomesVisibleByDrag(secondaryApp)
@Presubmit
@Test
- fun primaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(primaryApp)
+ fun primaryAppWindowIsVisibleAtEnd() = flicker.appWindowIsVisibleAtEnd(primaryApp)
@Presubmit
@Test
- fun secondaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(secondaryApp)
+ fun secondaryAppWindowBecomesVisible() = flicker.appWindowBecomesVisible(secondaryApp)
/** {@inheritDoc} */
- @Postsubmit
- @Test
- override fun entireScreenCovered() =
- super.entireScreenCovered()
+ @Postsubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun navBarLayerIsVisibleAtStartAndEnd() =
- super.navBarLayerIsVisibleAtStartAndEnd()
+ override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun navBarLayerPositionAtStartAndEnd() =
- super.navBarLayerPositionAtStartAndEnd()
+ override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun navBarWindowIsAlwaysVisible() =
- super.navBarWindowIsAlwaysVisible()
+ override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
/** {@inheritDoc} */
@Postsubmit
@@ -159,26 +151,22 @@ class EnterSplitScreenByDragFromAllApps(
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun statusBarLayerPositionAtStartAndEnd() =
- super.statusBarLayerPositionAtStartAndEnd()
+ override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun statusBarWindowIsAlwaysVisible() =
- super.statusBarWindowIsAlwaysVisible()
+ override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun taskBarLayerIsVisibleAtStartAndEnd() =
- super.taskBarLayerIsVisibleAtStartAndEnd()
+ override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun taskBarWindowIsAlwaysVisible() =
- super.taskBarWindowIsAlwaysVisible()
+ override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible()
/** {@inheritDoc} */
@Postsubmit
@@ -195,11 +183,11 @@ class EnterSplitScreenByDragFromAllApps(
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
// TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
- supportedNavigationModes =
- listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY))
+ supportedNavigationModes = listOf(PlatformConsts.NavBar.MODE_GESTURAL)
+ )
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
index 0c03d315cca2..5d37e858c15f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
@@ -19,13 +19,13 @@ package com.android.wm.shell.flicker.splitscreen
import android.platform.test.annotations.IwTest
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
-import android.view.WindowManagerPolicyConstants
import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import com.android.server.wm.traces.common.service.PlatformConsts
import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
import com.android.wm.shell.flicker.layerBecomesVisible
@@ -43,8 +43,8 @@ import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
/**
- * Test enter split screen by dragging app icon from notification.
- * This test is only for large screen devices.
+ * Test enter split screen by dragging app icon from notification. This test is only for large
+ * screen devices.
*
* To run this test: `atest WMShellFlickerTests:EnterSplitScreenByDragFromNotification`
*/
@@ -52,9 +52,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class EnterSplitScreenByDragFromNotification(
- testSpec: FlickerTestParameter
-) : SplitScreenBase(testSpec) {
+class EnterSplitScreenByDragFromNotification(flicker: FlickerTest) : SplitScreenBase(flicker) {
private val sendNotificationApp = SplitScreenUtils.getSendNotification(instrumentation)
@@ -78,21 +76,19 @@ class EnterSplitScreenByDragFromNotification(
SplitScreenUtils.dragFromNotificationToSplit(instrumentation, device, wmHelper)
SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, sendNotificationApp)
}
- teardown {
- sendNotificationApp.exit(wmHelper)
- }
+ teardown { sendNotificationApp.exit(wmHelper) }
}
@IwTest(focusArea = "sysui")
@Presubmit
@Test
- fun cujCompleted() = testSpec.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = false)
+ fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = false)
@Presubmit
@Test
fun splitScreenDividerBecomesVisible() {
Assume.assumeFalse(isShellTransitionsEnabled)
- testSpec.splitScreenDividerBecomesVisible()
+ flicker.splitScreenDividerBecomesVisible()
}
// TODO(b/245472831): Back to splitScreenDividerBecomesVisible after shell transition ready.
@@ -100,20 +96,16 @@ class EnterSplitScreenByDragFromNotification(
@Test
fun splitScreenDividerIsVisibleAtEnd_ShellTransit() {
Assume.assumeTrue(isShellTransitionsEnabled)
- testSpec.assertLayersEnd {
- this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
- }
+ flicker.assertLayersEnd { this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) }
}
- @Presubmit
- @Test
- fun primaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(primaryApp)
+ @Presubmit @Test fun primaryAppLayerIsVisibleAtEnd() = flicker.layerIsVisibleAtEnd(primaryApp)
@Presubmit
@Test
fun secondaryAppLayerBecomesVisible() {
Assume.assumeFalse(isShellTransitionsEnabled)
- testSpec.assertLayers {
+ flicker.assertLayers {
this.isInvisible(sendNotificationApp)
.then()
.isVisible(sendNotificationApp)
@@ -129,50 +121,48 @@ class EnterSplitScreenByDragFromNotification(
@Test
fun secondaryAppLayerBecomesVisible_ShellTransit() {
Assume.assumeTrue(isShellTransitionsEnabled)
- testSpec.layerBecomesVisible(sendNotificationApp)
+ flicker.layerBecomesVisible(sendNotificationApp)
}
@Presubmit
@Test
- fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
- primaryApp, landscapePosLeft = false, portraitPosTop = false)
+ fun primaryAppBoundsIsVisibleAtEnd() =
+ flicker.splitAppLayerBoundsIsVisibleAtEnd(
+ primaryApp,
+ landscapePosLeft = false,
+ portraitPosTop = false
+ )
@Presubmit
@Test
- fun secondaryAppBoundsBecomesVisible() = testSpec.splitAppLayerBoundsBecomesVisibleByDrag(
- sendNotificationApp)
+ fun secondaryAppBoundsBecomesVisible() =
+ flicker.splitAppLayerBoundsBecomesVisibleByDrag(sendNotificationApp)
@Presubmit
@Test
- fun primaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(primaryApp)
+ fun primaryAppWindowIsVisibleAtEnd() = flicker.appWindowIsVisibleAtEnd(primaryApp)
@Presubmit
@Test
- fun secondaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(sendNotificationApp)
+ fun secondaryAppWindowIsVisibleAtEnd() = flicker.appWindowIsVisibleAtEnd(sendNotificationApp)
/** {@inheritDoc} */
- @Postsubmit
- @Test
- override fun entireScreenCovered() =
- super.entireScreenCovered()
+ @Postsubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun navBarLayerIsVisibleAtStartAndEnd() =
- super.navBarLayerIsVisibleAtStartAndEnd()
+ override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun navBarLayerPositionAtStartAndEnd() =
- super.navBarLayerPositionAtStartAndEnd()
+ override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun navBarWindowIsAlwaysVisible() =
- super.navBarWindowIsAlwaysVisible()
+ override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
/** {@inheritDoc} */
@Postsubmit
@@ -183,26 +173,22 @@ class EnterSplitScreenByDragFromNotification(
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun statusBarLayerPositionAtStartAndEnd() =
- super.statusBarLayerPositionAtStartAndEnd()
+ override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun statusBarWindowIsAlwaysVisible() =
- super.statusBarWindowIsAlwaysVisible()
+ override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun taskBarLayerIsVisibleAtStartAndEnd() =
- super.taskBarLayerIsVisibleAtStartAndEnd()
+ override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun taskBarWindowIsAlwaysVisible() =
- super.taskBarWindowIsAlwaysVisible()
+ override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible()
/** {@inheritDoc} */
@Postsubmit
@@ -219,11 +205,10 @@ class EnterSplitScreenByDragFromNotification(
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
// TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
- supportedNavigationModes =
- listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY)
+ supportedNavigationModes = listOf(PlatformConsts.NavBar.MODE_GESTURAL)
)
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt
index dcadb5aa9cf4..abf9426aaa73 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt
@@ -17,14 +17,14 @@
package com.android.wm.shell.flicker.splitscreen
import android.platform.test.annotations.IwTest
-import android.view.WindowManagerPolicyConstants
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import com.android.server.wm.traces.common.service.PlatformConsts
import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
import com.android.wm.shell.flicker.layerBecomesVisible
import com.android.wm.shell.flicker.layerIsVisibleAtEnd
@@ -41,8 +41,7 @@ import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
/**
- * Test enter split screen by dragging a shortcut.
- * This test is only for large screen devices.
+ * Test enter split screen by dragging a shortcut. This test is only for large screen devices.
*
* To run this test: `atest WMShellFlickerTests:EnterSplitScreenByDragFromShortcut`
*/
@@ -50,13 +49,11 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class EnterSplitScreenByDragFromShortcut(
- testSpec: FlickerTestParameter
-) : SplitScreenBase(testSpec) {
+class EnterSplitScreenByDragFromShortcut(flicker: FlickerTest) : SplitScreenBase(flicker) {
@Before
fun before() {
- Assume.assumeTrue(testSpec.isTablet)
+ Assume.assumeTrue(flicker.scenario.isTablet)
}
override val transition: FlickerBuilder.() -> Unit
@@ -80,39 +77,46 @@ class EnterSplitScreenByDragFromShortcut(
@IwTest(focusArea = "sysui")
@Presubmit
@Test
- fun cujCompleted() = testSpec.splitScreenEntered(primaryApp, secondaryApp,
- fromOtherApp = false, appExistAtStart = false)
+ fun cujCompleted() =
+ flicker.splitScreenEntered(
+ primaryApp,
+ secondaryApp,
+ fromOtherApp = false,
+ appExistAtStart = false
+ )
@Presubmit
@Test
- fun splitScreenDividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible()
+ fun splitScreenDividerBecomesVisible() = flicker.splitScreenDividerBecomesVisible()
- @Presubmit
- @Test
- fun primaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(primaryApp)
+ @Presubmit @Test fun primaryAppLayerIsVisibleAtEnd() = flicker.layerIsVisibleAtEnd(primaryApp)
@Presubmit
@Test
- fun secondaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(secondaryApp)
+ fun secondaryAppLayerBecomesVisible() = flicker.layerBecomesVisible(secondaryApp)
@Presubmit
@Test
- fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
- primaryApp, landscapePosLeft = false, portraitPosTop = false)
+ fun primaryAppBoundsIsVisibleAtEnd() =
+ flicker.splitAppLayerBoundsIsVisibleAtEnd(
+ primaryApp,
+ landscapePosLeft = false,
+ portraitPosTop = false
+ )
@Presubmit
@Test
- fun secondaryAppBoundsBecomesVisible() = testSpec.splitAppLayerBoundsBecomesVisibleByDrag(
- secondaryApp)
+ fun secondaryAppBoundsBecomesVisible() =
+ flicker.splitAppLayerBoundsBecomesVisibleByDrag(secondaryApp)
@Presubmit
@Test
- fun primaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(primaryApp)
+ fun primaryAppWindowIsVisibleAtEnd() = flicker.appWindowIsVisibleAtEnd(primaryApp)
@Presubmit
@Test
fun secondaryAppWindowBecomesVisible() {
- testSpec.assertWm {
+ flicker.assertWm {
this.notContains(secondaryApp)
.then()
.isAppWindowInvisible(secondaryApp, isOptional = true)
@@ -122,28 +126,22 @@ class EnterSplitScreenByDragFromShortcut(
}
/** {@inheritDoc} */
- @Postsubmit
- @Test
- override fun entireScreenCovered() =
- super.entireScreenCovered()
+ @Postsubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun navBarLayerIsVisibleAtStartAndEnd() =
- super.navBarLayerIsVisibleAtStartAndEnd()
+ override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun navBarLayerPositionAtStartAndEnd() =
- super.navBarLayerPositionAtStartAndEnd()
+ override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun navBarWindowIsAlwaysVisible() =
- super.navBarWindowIsAlwaysVisible()
+ override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
/** {@inheritDoc} */
@Postsubmit
@@ -154,26 +152,22 @@ class EnterSplitScreenByDragFromShortcut(
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun statusBarLayerPositionAtStartAndEnd() =
- super.statusBarLayerPositionAtStartAndEnd()
+ override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun statusBarWindowIsAlwaysVisible() =
- super.statusBarWindowIsAlwaysVisible()
+ override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun taskBarLayerIsVisibleAtStartAndEnd() =
- super.taskBarLayerIsVisibleAtStartAndEnd()
+ override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun taskBarWindowIsAlwaysVisible() =
- super.taskBarWindowIsAlwaysVisible()
+ override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible()
/** {@inheritDoc} */
@Postsubmit
@@ -190,11 +184,11 @@ class EnterSplitScreenByDragFromShortcut(
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
// TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
- supportedNavigationModes =
- listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY))
+ supportedNavigationModes = listOf(PlatformConsts.NavBar.MODE_GESTURAL)
+ )
}
}
-} \ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
index 496d439b0ec6..795a2c4f43ba 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
@@ -19,13 +19,13 @@ package com.android.wm.shell.flicker.splitscreen
import android.platform.test.annotations.IwTest
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
-import android.view.WindowManagerPolicyConstants
import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import com.android.server.wm.traces.common.service.PlatformConsts
import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
import com.android.wm.shell.flicker.appWindowBecomesVisible
import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
@@ -44,8 +44,8 @@ import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
/**
- * Test enter split screen by dragging app icon from taskbar.
- * This test is only for large screen devices.
+ * Test enter split screen by dragging app icon from taskbar. This test is only for large screen
+ * devices.
*
* To run this test: `atest WMShellFlickerTests:EnterSplitScreenByDragFromTaskbar`
*/
@@ -53,9 +53,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class EnterSplitScreenByDragFromTaskbar(
- testSpec: FlickerTestParameter
-) : SplitScreenBase(testSpec) {
+class EnterSplitScreenByDragFromTaskbar(flicker: FlickerTest) : SplitScreenBase(flicker) {
@Before
fun before() {
@@ -68,9 +66,7 @@ class EnterSplitScreenByDragFromTaskbar(
super.transition(this)
setup {
tapl.goHome()
- SplitScreenUtils.createShortcutOnHotseatIfNotExist(
- tapl, secondaryApp.appName
- )
+ SplitScreenUtils.createShortcutOnHotseatIfNotExist(tapl, secondaryApp.appName)
primaryApp.launchViaIntent(wmHelper)
}
transitions {
@@ -84,13 +80,13 @@ class EnterSplitScreenByDragFromTaskbar(
@IwTest(focusArea = "sysui")
@Presubmit
@Test
- fun cujCompleted() = testSpec.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = false)
+ fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = false)
@Presubmit
@Test
fun splitScreenDividerBecomesVisible() {
Assume.assumeFalse(isShellTransitionsEnabled)
- testSpec.splitScreenDividerBecomesVisible()
+ flicker.splitScreenDividerBecomesVisible()
}
// TODO(b/245472831): Back to splitScreenDividerBecomesVisible after shell transition ready.
@@ -98,20 +94,16 @@ class EnterSplitScreenByDragFromTaskbar(
@Test
fun splitScreenDividerIsVisibleAtEnd_ShellTransit() {
Assume.assumeTrue(isShellTransitionsEnabled)
- testSpec.assertLayersEnd {
- this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
- }
+ flicker.assertLayersEnd { this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) }
}
- @Presubmit
- @Test
- fun primaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(primaryApp)
+ @Presubmit @Test fun primaryAppLayerIsVisibleAtEnd() = flicker.layerIsVisibleAtEnd(primaryApp)
@Presubmit
@Test
fun secondaryAppLayerBecomesVisible() {
Assume.assumeFalse(isShellTransitionsEnabled)
- testSpec.assertLayers {
+ flicker.assertLayers {
this.isInvisible(secondaryApp)
.then()
.isVisible(secondaryApp)
@@ -127,50 +119,48 @@ class EnterSplitScreenByDragFromTaskbar(
@Test
fun secondaryAppLayerBecomesVisible_ShellTransit() {
Assume.assumeTrue(isShellTransitionsEnabled)
- testSpec.layerBecomesVisible(secondaryApp)
+ flicker.layerBecomesVisible(secondaryApp)
}
@Presubmit
@Test
- fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
- primaryApp, landscapePosLeft = false, portraitPosTop = false)
+ fun primaryAppBoundsIsVisibleAtEnd() =
+ flicker.splitAppLayerBoundsIsVisibleAtEnd(
+ primaryApp,
+ landscapePosLeft = false,
+ portraitPosTop = false
+ )
@Presubmit
@Test
- fun secondaryAppBoundsBecomesVisible() = testSpec.splitAppLayerBoundsBecomesVisibleByDrag(
- secondaryApp)
+ fun secondaryAppBoundsBecomesVisible() =
+ flicker.splitAppLayerBoundsBecomesVisibleByDrag(secondaryApp)
@Presubmit
@Test
- fun primaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(primaryApp)
+ fun primaryAppWindowIsVisibleAtEnd() = flicker.appWindowIsVisibleAtEnd(primaryApp)
@Presubmit
@Test
- fun secondaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(secondaryApp)
+ fun secondaryAppWindowBecomesVisible() = flicker.appWindowBecomesVisible(secondaryApp)
/** {@inheritDoc} */
- @Postsubmit
- @Test
- override fun entireScreenCovered() =
- super.entireScreenCovered()
+ @Postsubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun navBarLayerIsVisibleAtStartAndEnd() =
- super.navBarLayerIsVisibleAtStartAndEnd()
+ override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun navBarLayerPositionAtStartAndEnd() =
- super.navBarLayerPositionAtStartAndEnd()
+ override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun navBarWindowIsAlwaysVisible() =
- super.navBarWindowIsAlwaysVisible()
+ override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
/** {@inheritDoc} */
@Postsubmit
@@ -181,26 +171,22 @@ class EnterSplitScreenByDragFromTaskbar(
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun statusBarLayerPositionAtStartAndEnd() =
- super.statusBarLayerPositionAtStartAndEnd()
+ override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun statusBarWindowIsAlwaysVisible() =
- super.statusBarWindowIsAlwaysVisible()
+ override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun taskBarLayerIsVisibleAtStartAndEnd() =
- super.taskBarLayerIsVisibleAtStartAndEnd()
+ override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun taskBarWindowIsAlwaysVisible() =
- super.taskBarWindowIsAlwaysVisible()
+ override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible()
/** {@inheritDoc} */
@Postsubmit
@@ -217,10 +203,9 @@ class EnterSplitScreenByDragFromTaskbar(
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
- supportedNavigationModes =
- listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY)
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ supportedNavigationModes = listOf(PlatformConsts.NavBar.MODE_GESTURAL)
)
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt
index fb7b8b7926e3..c09ca914caff 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt
@@ -21,11 +21,11 @@ import android.platform.test.annotations.IwTest
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.wm.shell.flicker.appWindowBecomesVisible
import com.android.wm.shell.flicker.layerBecomesVisible
import com.android.wm.shell.flicker.layerIsVisibleAtEnd
@@ -49,7 +49,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class EnterSplitScreenFromOverview(testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) {
+class EnterSplitScreenFromOverview(flicker: FlickerTest) : SplitScreenBase(flicker) {
override val transition: FlickerBuilder.() -> Unit
get() = {
super.transition(this)
@@ -57,7 +57,8 @@ class EnterSplitScreenFromOverview(testSpec: FlickerTestParameter) : SplitScreen
primaryApp.launchViaIntent(wmHelper)
secondaryApp.launchViaIntent(wmHelper)
tapl.goHome()
- wmHelper.StateSyncBuilder()
+ wmHelper
+ .StateSyncBuilder()
.withAppTransitionIdle()
.withHomeActivityVisible()
.waitForAndVerify()
@@ -71,72 +72,76 @@ class EnterSplitScreenFromOverview(testSpec: FlickerTestParameter) : SplitScreen
@IwTest(focusArea = "sysui")
@Presubmit
@Test
- fun cujCompleted() = testSpec.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
+ fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
@Presubmit
@Test
- fun splitScreenDividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible()
+ fun splitScreenDividerBecomesVisible() = flicker.splitScreenDividerBecomesVisible()
- @Presubmit
- @Test
- fun primaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(primaryApp)
+ @Presubmit @Test fun primaryAppLayerIsVisibleAtEnd() = flicker.layerIsVisibleAtEnd(primaryApp)
@Presubmit
@Test
- fun secondaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(secondaryApp)
+ fun secondaryAppLayerBecomesVisible() = flicker.layerBecomesVisible(secondaryApp)
@Presubmit
@Test
- fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
- primaryApp, landscapePosLeft = tapl.isTablet, portraitPosTop = false)
+ fun primaryAppBoundsIsVisibleAtEnd() =
+ flicker.splitAppLayerBoundsIsVisibleAtEnd(
+ primaryApp,
+ landscapePosLeft = tapl.isTablet,
+ portraitPosTop = false
+ )
@Presubmit
@Test
fun secondaryAppBoundsBecomesVisible() {
Assume.assumeFalse(isShellTransitionsEnabled)
- testSpec.splitAppLayerBoundsBecomesVisible(
- secondaryApp, landscapePosLeft = !tapl.isTablet, portraitPosTop = true)
+ flicker.splitAppLayerBoundsBecomesVisible(
+ secondaryApp,
+ landscapePosLeft = !tapl.isTablet,
+ portraitPosTop = true
+ )
}
@FlakyTest(bugId = 244407465)
@Test
fun secondaryAppBoundsBecomesVisible_shellTransit() {
Assume.assumeTrue(isShellTransitionsEnabled)
- testSpec.splitAppLayerBoundsBecomesVisible(
- secondaryApp, landscapePosLeft = !tapl.isTablet, portraitPosTop = true)
+ flicker.splitAppLayerBoundsBecomesVisible(
+ secondaryApp,
+ landscapePosLeft = !tapl.isTablet,
+ portraitPosTop = true
+ )
}
@Presubmit
@Test
- fun primaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(primaryApp)
+ fun primaryAppWindowBecomesVisible() = flicker.appWindowBecomesVisible(primaryApp)
@Presubmit
@Test
- fun secondaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(secondaryApp)
+ fun secondaryAppWindowBecomesVisible() = flicker.appWindowBecomesVisible(secondaryApp)
/** {@inheritDoc} */
@FlakyTest(bugId = 251269324)
@Test
- override fun entireScreenCovered() =
- super.entireScreenCovered()
+ override fun entireScreenCovered() = super.entireScreenCovered()
/** {@inheritDoc} */
@Presubmit
@Test
- override fun navBarLayerIsVisibleAtStartAndEnd() =
- super.navBarLayerIsVisibleAtStartAndEnd()
+ override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun navBarLayerPositionAtStartAndEnd() =
- super.navBarLayerPositionAtStartAndEnd()
+ override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
/** {@inheritDoc} */
@Presubmit
@Test
- override fun navBarWindowIsAlwaysVisible() =
- super.navBarWindowIsAlwaysVisible()
+ override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
/** {@inheritDoc} */
@Presubmit
@@ -147,26 +152,22 @@ class EnterSplitScreenFromOverview(testSpec: FlickerTestParameter) : SplitScreen
/** {@inheritDoc} */
@Presubmit
@Test
- override fun statusBarLayerPositionAtStartAndEnd() =
- super.statusBarLayerPositionAtStartAndEnd()
+ override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd()
/** {@inheritDoc} */
@Presubmit
@Test
- override fun statusBarWindowIsAlwaysVisible() =
- super.statusBarWindowIsAlwaysVisible()
+ override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible()
/** {@inheritDoc} */
@Presubmit
@Test
- override fun taskBarLayerIsVisibleAtStartAndEnd() =
- super.taskBarLayerIsVisibleAtStartAndEnd()
+ override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
/** {@inheritDoc} */
@Presubmit
@Test
- override fun taskBarWindowIsAlwaysVisible() =
- super.taskBarWindowIsAlwaysVisible()
+ override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible()
/** {@inheritDoc} */
@FlakyTest(bugId = 252736515)
@@ -183,8 +184,8 @@ class EnterSplitScreenFromOverview(testSpec: FlickerTestParameter) : SplitScreen
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests()
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests()
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt
index c8413337a1e6..8c0a303189e1 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt
@@ -17,12 +17,12 @@
package com.android.wm.shell.flicker.splitscreen
import android.content.Context
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.helpers.setRotation
import com.android.wm.shell.flicker.BaseTest
-abstract class SplitScreenBase(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
+abstract class SplitScreenBase(flicker: FlickerTest) : BaseTest(flicker) {
protected val context: Context = instrumentation.context
protected val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
protected val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
@@ -32,8 +32,8 @@ abstract class SplitScreenBase(testSpec: FlickerTestParameter) : BaseTest(testSp
get() = {
setup {
tapl.setEnableRotation(true)
- setRotation(testSpec.startRotation)
- tapl.setExpectedRotation(testSpec.startRotation)
+ setRotation(flicker.scenario.startRotation)
+ tapl.setExpectedRotation(flicker.scenario.startRotation.value)
tapl.workspace.switchToOverview().dismissAllTasks()
}
teardown {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt
index 4a3284e1953b..f3927d405467 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt
@@ -41,8 +41,8 @@ import com.android.server.wm.traces.parser.toFlickerComponent
import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
import com.android.wm.shell.flicker.LAUNCHER_UI_PACKAGE_NAME
import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME
-import org.junit.Assert.assertNotNull
import java.util.Collections
+import org.junit.Assert.assertNotNull
internal object SplitScreenUtils {
private const val TIMEOUT_MS = 3_000L
@@ -129,10 +129,18 @@ internal object SplitScreenUtils {
// Find the second task in the upper right corner in split select mode by sorting
// 'left' in descending order and 'top' in ascending order.
- Collections.sort(snapshots, { t1: UiObject2, t2: UiObject2 ->
- t2.getVisibleBounds().left - t1.getVisibleBounds().left})
- Collections.sort(snapshots, { t1: UiObject2, t2: UiObject2 ->
- t1.getVisibleBounds().top - t2.getVisibleBounds().top})
+ Collections.sort(
+ snapshots,
+ { t1: UiObject2, t2: UiObject2 ->
+ t2.getVisibleBounds().left - t1.getVisibleBounds().left
+ }
+ )
+ Collections.sort(
+ snapshots,
+ { t1: UiObject2, t2: UiObject2 ->
+ t1.getVisibleBounds().top - t2.getVisibleBounds().top
+ }
+ )
snapshots[0].click()
} else {
tapl.workspace
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
index f7610c48a0f8..09568b291830 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
@@ -19,13 +19,13 @@ package com.android.wm.shell.flicker.splitscreen
import android.platform.test.annotations.IwTest
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
-import android.view.WindowManagerPolicyConstants
import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.helpers.WindowUtils
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import com.android.server.wm.traces.common.service.PlatformConsts
import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
@@ -50,19 +50,15 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class SwitchAppByDoubleTapDivider(testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) {
+class SwitchAppByDoubleTapDivider(flicker: FlickerTest) : SplitScreenBase(flicker) {
override val transition: FlickerBuilder.() -> Unit
get() = {
super.transition(this)
- setup {
- SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
- }
+ setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) }
transitions {
SplitScreenUtils.doubleTapDividerToSwitch(device)
- wmHelper.StateSyncBuilder()
- .withAppTransitionIdle()
- .waitForAndVerify()
+ wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
waitForLayersToSwitch(wmHelper)
waitForWindowsToSwitch(wmHelper)
@@ -70,61 +66,74 @@ class SwitchAppByDoubleTapDivider(testSpec: FlickerTestParameter) : SplitScreenB
}
private fun waitForWindowsToSwitch(wmHelper: WindowManagerStateHelper) {
- wmHelper.StateSyncBuilder().add("appWindowsSwitched") {
- val primaryAppWindow = it.wmState.visibleWindows.firstOrNull { window ->
- primaryApp.windowMatchesAnyOf(window)
- } ?: return@add false
- val secondaryAppWindow = it.wmState.visibleWindows.firstOrNull { window ->
- secondaryApp.windowMatchesAnyOf(window)
- } ?: return@add false
-
- if (isLandscape(testSpec.endRotation)) {
- return@add if (testSpec.isTablet) {
- secondaryAppWindow.frame.right <= primaryAppWindow.frame.left
- } else {
- primaryAppWindow.frame.right <= secondaryAppWindow.frame.left
- }
- } else {
- return@add if (testSpec.isTablet) {
- primaryAppWindow.frame.bottom <= secondaryAppWindow.frame.top
+ wmHelper
+ .StateSyncBuilder()
+ .add("appWindowsSwitched") {
+ val primaryAppWindow =
+ it.wmState.visibleWindows.firstOrNull { window ->
+ primaryApp.windowMatchesAnyOf(window)
+ }
+ ?: return@add false
+ val secondaryAppWindow =
+ it.wmState.visibleWindows.firstOrNull { window ->
+ secondaryApp.windowMatchesAnyOf(window)
+ }
+ ?: return@add false
+
+ if (isLandscape(flicker.scenario.endRotation)) {
+ return@add if (flicker.scenario.isTablet) {
+ secondaryAppWindow.frame.right <= primaryAppWindow.frame.left
+ } else {
+ primaryAppWindow.frame.right <= secondaryAppWindow.frame.left
+ }
} else {
- primaryAppWindow.frame.bottom <= secondaryAppWindow.frame.top
+ return@add if (flicker.scenario.isTablet) {
+ primaryAppWindow.frame.bottom <= secondaryAppWindow.frame.top
+ } else {
+ primaryAppWindow.frame.bottom <= secondaryAppWindow.frame.top
+ }
}
}
- }.waitForAndVerify()
+ .waitForAndVerify()
}
private fun waitForLayersToSwitch(wmHelper: WindowManagerStateHelper) {
- wmHelper.StateSyncBuilder().add("appLayersSwitched") {
- val primaryAppLayer = it.layerState.visibleLayers.firstOrNull { window ->
- primaryApp.layerMatchesAnyOf(window)
- } ?: return@add false
- val secondaryAppLayer = it.layerState.visibleLayers.firstOrNull { window ->
- secondaryApp.layerMatchesAnyOf(window)
- } ?: return@add false
-
- val primaryVisibleRegion = primaryAppLayer.visibleRegion?.bounds
- ?: return@add false
- val secondaryVisibleRegion = secondaryAppLayer.visibleRegion?.bounds
- ?: return@add false
-
- if (isLandscape(testSpec.endRotation)) {
- return@add if (testSpec.isTablet) {
- secondaryVisibleRegion.right <= primaryVisibleRegion.left
- } else {
- primaryVisibleRegion.right <= secondaryVisibleRegion.left
- }
- } else {
- return@add if (testSpec.isTablet) {
- primaryVisibleRegion.bottom <= secondaryVisibleRegion.top
+ wmHelper
+ .StateSyncBuilder()
+ .add("appLayersSwitched") {
+ val primaryAppLayer =
+ it.layerState.visibleLayers.firstOrNull { window ->
+ primaryApp.layerMatchesAnyOf(window)
+ }
+ ?: return@add false
+ val secondaryAppLayer =
+ it.layerState.visibleLayers.firstOrNull { window ->
+ secondaryApp.layerMatchesAnyOf(window)
+ }
+ ?: return@add false
+
+ val primaryVisibleRegion = primaryAppLayer.visibleRegion?.bounds ?: return@add false
+ val secondaryVisibleRegion =
+ secondaryAppLayer.visibleRegion?.bounds ?: return@add false
+
+ if (isLandscape(flicker.scenario.endRotation)) {
+ return@add if (flicker.scenario.isTablet) {
+ secondaryVisibleRegion.right <= primaryVisibleRegion.left
+ } else {
+ primaryVisibleRegion.right <= secondaryVisibleRegion.left
+ }
} else {
- primaryVisibleRegion.bottom <= secondaryVisibleRegion.top
+ return@add if (flicker.scenario.isTablet) {
+ primaryVisibleRegion.bottom <= secondaryVisibleRegion.top
+ } else {
+ primaryVisibleRegion.bottom <= secondaryVisibleRegion.top
+ }
}
}
- }.waitForAndVerify()
+ .waitForAndVerify()
}
- private fun isLandscape(rotation: Int): Boolean {
+ private fun isLandscape(rotation: PlatformConsts.Rotation): Boolean {
val displayBounds = WindowUtils.getDisplayBounds(rotation)
return displayBounds.width > displayBounds.height
}
@@ -133,13 +142,13 @@ class SwitchAppByDoubleTapDivider(testSpec: FlickerTestParameter) : SplitScreenB
@Presubmit
@Test
fun cujCompleted() {
- testSpec.appWindowIsVisibleAtStart(primaryApp)
- testSpec.appWindowIsVisibleAtStart(secondaryApp)
- testSpec.splitScreenDividerIsVisibleAtStart()
+ flicker.appWindowIsVisibleAtStart(primaryApp)
+ flicker.appWindowIsVisibleAtStart(secondaryApp)
+ flicker.splitScreenDividerIsVisibleAtStart()
- testSpec.appWindowIsVisibleAtEnd(primaryApp)
- testSpec.appWindowIsVisibleAtEnd(secondaryApp)
- testSpec.splitScreenDividerIsVisibleAtEnd()
+ flicker.appWindowIsVisibleAtEnd(primaryApp)
+ flicker.appWindowIsVisibleAtEnd(secondaryApp)
+ flicker.splitScreenDividerIsVisibleAtEnd()
// TODO(b/246490534): Add validation for switched app after withAppTransitionIdle is
// robust enough to get the correct end state.
@@ -147,63 +156,57 @@ class SwitchAppByDoubleTapDivider(testSpec: FlickerTestParameter) : SplitScreenB
@Presubmit
@Test
- fun splitScreenDividerKeepVisible() = testSpec.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
+ fun splitScreenDividerKeepVisible() = flicker.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
- @Presubmit
- @Test
- fun primaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(primaryApp)
+ @Presubmit @Test fun primaryAppLayerIsVisibleAtEnd() = flicker.layerIsVisibleAtEnd(primaryApp)
@Presubmit
@Test
- fun secondaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(secondaryApp)
+ fun secondaryAppLayerIsVisibleAtEnd() = flicker.layerIsVisibleAtEnd(secondaryApp)
@Presubmit
@Test
- fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
- primaryApp,
- landscapePosLeft = !tapl.isTablet,
- portraitPosTop = true
- )
+ fun primaryAppBoundsIsVisibleAtEnd() =
+ flicker.splitAppLayerBoundsIsVisibleAtEnd(
+ primaryApp,
+ landscapePosLeft = !tapl.isTablet,
+ portraitPosTop = true
+ )
@Presubmit
@Test
- fun secondaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
- secondaryApp,
- landscapePosLeft = tapl.isTablet,
- portraitPosTop = false
- )
+ fun secondaryAppBoundsIsVisibleAtEnd() =
+ flicker.splitAppLayerBoundsIsVisibleAtEnd(
+ secondaryApp,
+ landscapePosLeft = tapl.isTablet,
+ portraitPosTop = false
+ )
@Presubmit
@Test
- fun primaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(primaryApp)
+ fun primaryAppWindowIsVisibleAtEnd() = flicker.appWindowIsVisibleAtEnd(primaryApp)
@Presubmit
@Test
- fun secondaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(secondaryApp)
+ fun secondaryAppWindowIsVisibleAtEnd() = flicker.appWindowIsVisibleAtEnd(secondaryApp)
/** {@inheritDoc} */
- @Postsubmit
- @Test
- override fun entireScreenCovered() =
- super.entireScreenCovered()
+ @Postsubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun navBarLayerIsVisibleAtStartAndEnd() =
- super.navBarLayerIsVisibleAtStartAndEnd()
+ override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun navBarLayerPositionAtStartAndEnd() =
- super.navBarLayerPositionAtStartAndEnd()
+ override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun navBarWindowIsAlwaysVisible() =
- super.navBarWindowIsAlwaysVisible()
+ override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
/** {@inheritDoc} */
@Postsubmit
@@ -214,26 +217,22 @@ class SwitchAppByDoubleTapDivider(testSpec: FlickerTestParameter) : SplitScreenB
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun statusBarLayerPositionAtStartAndEnd() =
- super.statusBarLayerPositionAtStartAndEnd()
+ override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun statusBarWindowIsAlwaysVisible() =
- super.statusBarWindowIsAlwaysVisible()
+ override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun taskBarLayerIsVisibleAtStartAndEnd() =
- super.taskBarLayerIsVisibleAtStartAndEnd()
+ override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun taskBarWindowIsAlwaysVisible() =
- super.taskBarWindowIsAlwaysVisible()
+ override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible()
/** {@inheritDoc} */
@Postsubmit
@@ -250,11 +249,10 @@ class SwitchAppByDoubleTapDivider(testSpec: FlickerTestParameter) : SplitScreenB
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
// TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
- supportedNavigationModes =
- listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY)
+ supportedNavigationModes = listOf(PlatformConsts.NavBar.MODE_GESTURAL)
)
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
index 993dba28bbc4..940e0e93d524 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
@@ -19,12 +19,12 @@ package com.android.wm.shell.flicker.splitscreen
import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.IwTest
import android.platform.test.annotations.Presubmit
-import android.view.WindowManagerPolicyConstants
import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import com.android.server.wm.traces.common.service.PlatformConsts
import com.android.wm.shell.flicker.appWindowBecomesVisible
import com.android.wm.shell.flicker.layerBecomesVisible
import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
@@ -45,7 +45,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class SwitchBackToSplitFromAnotherApp(testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) {
+class SwitchBackToSplitFromAnotherApp(flicker: FlickerTest) : SplitScreenBase(flicker) {
val thirdApp = SplitScreenUtils.getNonResizeable(instrumentation)
override val transition: FlickerBuilder.() -> Unit
@@ -66,22 +66,22 @@ class SwitchBackToSplitFromAnotherApp(testSpec: FlickerTestParameter) : SplitScr
@IwTest(focusArea = "sysui")
@Presubmit
@Test
- fun cujCompleted() = testSpec.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
+ fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
@Presubmit
@Test
- fun splitScreenDividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible()
+ fun splitScreenDividerBecomesVisible() = flicker.splitScreenDividerBecomesVisible()
- @Presubmit @Test fun primaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(primaryApp)
+ @Presubmit @Test fun primaryAppLayerBecomesVisible() = flicker.layerBecomesVisible(primaryApp)
@Presubmit
@Test
- fun secondaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(secondaryApp)
+ fun secondaryAppLayerBecomesVisible() = flicker.layerBecomesVisible(secondaryApp)
@Presubmit
@Test
fun primaryAppBoundsIsVisibleAtEnd() =
- testSpec.splitAppLayerBoundsIsVisibleAtEnd(
+ flicker.splitAppLayerBoundsIsVisibleAtEnd(
primaryApp,
landscapePosLeft = tapl.isTablet,
portraitPosTop = false
@@ -90,7 +90,7 @@ class SwitchBackToSplitFromAnotherApp(testSpec: FlickerTestParameter) : SplitScr
@Presubmit
@Test
fun secondaryAppBoundsIsVisibleAtEnd() =
- testSpec.splitAppLayerBoundsIsVisibleAtEnd(
+ flicker.splitAppLayerBoundsIsVisibleAtEnd(
secondaryApp,
landscapePosLeft = !tapl.isTablet,
portraitPosTop = true
@@ -98,17 +98,14 @@ class SwitchBackToSplitFromAnotherApp(testSpec: FlickerTestParameter) : SplitScr
@Presubmit
@Test
- fun primaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(primaryApp)
+ fun primaryAppWindowBecomesVisible() = flicker.appWindowBecomesVisible(primaryApp)
@Presubmit
@Test
- fun secondaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(secondaryApp)
+ fun secondaryAppWindowBecomesVisible() = flicker.appWindowBecomesVisible(secondaryApp)
/** {@inheritDoc} */
- @FlakyTest
- @Test
- override fun entireScreenCovered() =
- super.entireScreenCovered()
+ @FlakyTest @Test override fun entireScreenCovered() = super.entireScreenCovered()
/** {@inheritDoc} */
@Presubmit
@@ -166,13 +163,11 @@ class SwitchBackToSplitFromAnotherApp(testSpec: FlickerTestParameter) : SplitScr
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(
- // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
- supportedNavigationModes =
- listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY)
- )
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
+ supportedNavigationModes = listOf(PlatformConsts.NavBar.MODE_GESTURAL)
+ )
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
index 2a552cdd67e8..85812c420f3b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
@@ -19,12 +19,12 @@ package com.android.wm.shell.flicker.splitscreen
import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.IwTest
import android.platform.test.annotations.Presubmit
-import android.view.WindowManagerPolicyConstants
import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import com.android.server.wm.traces.common.service.PlatformConsts
import com.android.wm.shell.flicker.appWindowBecomesVisible
import com.android.wm.shell.flicker.layerBecomesVisible
import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
@@ -45,7 +45,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class SwitchBackToSplitFromHome(testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) {
+class SwitchBackToSplitFromHome(flicker: FlickerTest) : SplitScreenBase(flicker) {
override val transition: FlickerBuilder.() -> Unit
get() = {
@@ -65,22 +65,22 @@ class SwitchBackToSplitFromHome(testSpec: FlickerTestParameter) : SplitScreenBas
@IwTest(focusArea = "sysui")
@Presubmit
@Test
- fun cujCompleted() = testSpec.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
+ fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
@Presubmit
@Test
- fun splitScreenDividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible()
+ fun splitScreenDividerBecomesVisible() = flicker.splitScreenDividerBecomesVisible()
- @Presubmit @Test fun primaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(primaryApp)
+ @Presubmit @Test fun primaryAppLayerBecomesVisible() = flicker.layerBecomesVisible(primaryApp)
@Presubmit
@Test
- fun secondaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(secondaryApp)
+ fun secondaryAppLayerBecomesVisible() = flicker.layerBecomesVisible(secondaryApp)
@Presubmit
@Test
fun primaryAppBoundsIsVisibleAtEnd() =
- testSpec.splitAppLayerBoundsIsVisibleAtEnd(
+ flicker.splitAppLayerBoundsIsVisibleAtEnd(
primaryApp,
landscapePosLeft = tapl.isTablet,
portraitPosTop = false
@@ -89,7 +89,7 @@ class SwitchBackToSplitFromHome(testSpec: FlickerTestParameter) : SplitScreenBas
@Presubmit
@Test
fun secondaryAppBoundsIsVisibleAtEnd() =
- testSpec.splitAppLayerBoundsIsVisibleAtEnd(
+ flicker.splitAppLayerBoundsIsVisibleAtEnd(
secondaryApp,
landscapePosLeft = !tapl.isTablet,
portraitPosTop = true
@@ -97,17 +97,14 @@ class SwitchBackToSplitFromHome(testSpec: FlickerTestParameter) : SplitScreenBas
@Presubmit
@Test
- fun primaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(primaryApp)
+ fun primaryAppWindowBecomesVisible() = flicker.appWindowBecomesVisible(primaryApp)
@Presubmit
@Test
- fun secondaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(secondaryApp)
+ fun secondaryAppWindowBecomesVisible() = flicker.appWindowBecomesVisible(secondaryApp)
/** {@inheritDoc} */
- @FlakyTest
- @Test
- override fun entireScreenCovered() =
- super.entireScreenCovered()
+ @FlakyTest @Test override fun entireScreenCovered() = super.entireScreenCovered()
/** {@inheritDoc} */
@Presubmit
@@ -165,13 +162,11 @@ class SwitchBackToSplitFromHome(testSpec: FlickerTestParameter) : SplitScreenBas
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(
- // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
- supportedNavigationModes =
- listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY)
- )
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
+ supportedNavigationModes = listOf(PlatformConsts.NavBar.MODE_GESTURAL)
+ )
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
index 7f81baef315b..7c62433d8905 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
@@ -19,12 +19,12 @@ package com.android.wm.shell.flicker.splitscreen
import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.IwTest
import android.platform.test.annotations.Presubmit
-import android.view.WindowManagerPolicyConstants
import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import com.android.server.wm.traces.common.service.PlatformConsts
import com.android.wm.shell.flicker.appWindowBecomesVisible
import com.android.wm.shell.flicker.layerBecomesVisible
import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
@@ -45,7 +45,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class SwitchBackToSplitFromRecent(testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) {
+class SwitchBackToSplitFromRecent(flicker: FlickerTest) : SplitScreenBase(flicker) {
override val transition: FlickerBuilder.() -> Unit
get() = {
@@ -65,22 +65,22 @@ class SwitchBackToSplitFromRecent(testSpec: FlickerTestParameter) : SplitScreenB
@IwTest(focusArea = "sysui")
@Presubmit
@Test
- fun cujCompleted() = testSpec.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
+ fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
@Presubmit
@Test
- fun splitScreenDividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible()
+ fun splitScreenDividerBecomesVisible() = flicker.splitScreenDividerBecomesVisible()
- @Presubmit @Test fun primaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(primaryApp)
+ @Presubmit @Test fun primaryAppLayerBecomesVisible() = flicker.layerBecomesVisible(primaryApp)
@Presubmit
@Test
- fun secondaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(secondaryApp)
+ fun secondaryAppLayerBecomesVisible() = flicker.layerBecomesVisible(secondaryApp)
@Presubmit
@Test
fun primaryAppBoundsIsVisibleAtEnd() =
- testSpec.splitAppLayerBoundsIsVisibleAtEnd(
+ flicker.splitAppLayerBoundsIsVisibleAtEnd(
primaryApp,
landscapePosLeft = tapl.isTablet,
portraitPosTop = false
@@ -89,7 +89,7 @@ class SwitchBackToSplitFromRecent(testSpec: FlickerTestParameter) : SplitScreenB
@Presubmit
@Test
fun secondaryAppBoundsIsVisibleAtEnd() =
- testSpec.splitAppLayerBoundsIsVisibleAtEnd(
+ flicker.splitAppLayerBoundsIsVisibleAtEnd(
secondaryApp,
landscapePosLeft = !tapl.isTablet,
portraitPosTop = true
@@ -97,17 +97,14 @@ class SwitchBackToSplitFromRecent(testSpec: FlickerTestParameter) : SplitScreenB
@Presubmit
@Test
- fun primaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(primaryApp)
+ fun primaryAppWindowBecomesVisible() = flicker.appWindowBecomesVisible(primaryApp)
@Presubmit
@Test
- fun secondaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(secondaryApp)
+ fun secondaryAppWindowBecomesVisible() = flicker.appWindowBecomesVisible(secondaryApp)
/** {@inheritDoc} */
- @Presubmit
- @Test
- override fun entireScreenCovered() =
- super.entireScreenCovered()
+ @Presubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
/** {@inheritDoc} */
@Presubmit
@@ -165,13 +162,11 @@ class SwitchBackToSplitFromRecent(testSpec: FlickerTestParameter) : SplitScreenB
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(
- // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
- supportedNavigationModes =
- listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY)
- )
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
+ supportedNavigationModes = listOf(PlatformConsts.NavBar.MODE_GESTURAL)
+ )
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt
index f5f5fd818f05..193ab98cf191 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt
@@ -20,22 +20,22 @@ import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.IwTest
import android.platform.test.annotations.Presubmit
import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
import com.android.wm.shell.flicker.appWindowBecomesInvisible
import com.android.wm.shell.flicker.appWindowBecomesVisible
import com.android.wm.shell.flicker.appWindowIsInvisibleAtEnd
-import com.android.wm.shell.flicker.appWindowIsVisibleAtStart
import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
+import com.android.wm.shell.flicker.appWindowIsVisibleAtStart
import com.android.wm.shell.flicker.layerBecomesInvisible
import com.android.wm.shell.flicker.layerBecomesVisible
import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
import com.android.wm.shell.flicker.splitAppLayerBoundsSnapToDivider
-import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtStart
import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtEnd
+import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtStart
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -51,7 +51,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class SwitchBetweenSplitPairs(testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) {
+class SwitchBetweenSplitPairs(flicker: FlickerTest) : SplitScreenBase(flicker) {
private val thirdApp = SplitScreenUtils.getIme(instrumentation)
private val fourthApp = SplitScreenUtils.getSendNotification(instrumentation)
@@ -77,21 +77,21 @@ class SwitchBetweenSplitPairs(testSpec: FlickerTestParameter) : SplitScreenBase(
@Presubmit
@Test
fun cujCompleted() {
- testSpec.appWindowIsVisibleAtStart(thirdApp)
- testSpec.appWindowIsVisibleAtStart(fourthApp)
- testSpec.splitScreenDividerIsVisibleAtStart()
-
- testSpec.appWindowIsVisibleAtEnd(primaryApp)
- testSpec.appWindowIsVisibleAtEnd(secondaryApp)
- testSpec.appWindowIsInvisibleAtEnd(thirdApp)
- testSpec.appWindowIsInvisibleAtEnd(fourthApp)
- testSpec.splitScreenDividerIsVisibleAtEnd()
+ flicker.appWindowIsVisibleAtStart(thirdApp)
+ flicker.appWindowIsVisibleAtStart(fourthApp)
+ flicker.splitScreenDividerIsVisibleAtStart()
+
+ flicker.appWindowIsVisibleAtEnd(primaryApp)
+ flicker.appWindowIsVisibleAtEnd(secondaryApp)
+ flicker.appWindowIsInvisibleAtEnd(thirdApp)
+ flicker.appWindowIsInvisibleAtEnd(fourthApp)
+ flicker.splitScreenDividerIsVisibleAtEnd()
}
@Presubmit
@Test
fun splitScreenDividerInvisibleAtMiddle() =
- testSpec.assertLayers {
+ flicker.assertLayers {
this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
.then()
.isInvisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
@@ -101,24 +101,24 @@ class SwitchBetweenSplitPairs(testSpec: FlickerTestParameter) : SplitScreenBase(
@FlakyTest(bugId = 247095572)
@Test
- fun primaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(primaryApp)
+ fun primaryAppLayerBecomesVisible() = flicker.layerBecomesVisible(primaryApp)
@FlakyTest(bugId = 247095572)
@Test
- fun secondaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(secondaryApp)
+ fun secondaryAppLayerBecomesVisible() = flicker.layerBecomesVisible(secondaryApp)
@FlakyTest(bugId = 247095572)
@Test
- fun thirdAppLayerBecomesInvisible() = testSpec.layerBecomesInvisible(thirdApp)
+ fun thirdAppLayerBecomesInvisible() = flicker.layerBecomesInvisible(thirdApp)
@FlakyTest(bugId = 247095572)
@Test
- fun fourthAppLayerBecomesInvisible() = testSpec.layerBecomesInvisible(fourthApp)
+ fun fourthAppLayerBecomesInvisible() = flicker.layerBecomesInvisible(fourthApp)
@Presubmit
@Test
fun primaryAppBoundsIsVisibleAtEnd() =
- testSpec.splitAppLayerBoundsIsVisibleAtEnd(
+ flicker.splitAppLayerBoundsIsVisibleAtEnd(
primaryApp,
landscapePosLeft = tapl.isTablet,
portraitPosTop = false
@@ -127,7 +127,7 @@ class SwitchBetweenSplitPairs(testSpec: FlickerTestParameter) : SplitScreenBase(
@Presubmit
@Test
fun secondaryAppBoundsIsVisibleAtEnd() =
- testSpec.splitAppLayerBoundsIsVisibleAtEnd(
+ flicker.splitAppLayerBoundsIsVisibleAtEnd(
secondaryApp,
landscapePosLeft = !tapl.isTablet,
portraitPosTop = true
@@ -136,66 +136,62 @@ class SwitchBetweenSplitPairs(testSpec: FlickerTestParameter) : SplitScreenBase(
@Presubmit
@Test
fun thirdAppBoundsIsVisibleAtBegin() =
- testSpec.assertLayersStart {
+ flicker.assertLayersStart {
this.splitAppLayerBoundsSnapToDivider(
thirdApp,
landscapePosLeft = tapl.isTablet,
portraitPosTop = false,
- testSpec.startRotation
+ flicker.scenario.startRotation
)
}
@Presubmit
@Test
fun fourthAppBoundsIsVisibleAtBegin() =
- testSpec.assertLayersStart {
+ flicker.assertLayersStart {
this.splitAppLayerBoundsSnapToDivider(
fourthApp,
landscapePosLeft = !tapl.isTablet,
portraitPosTop = true,
- testSpec.startRotation
+ flicker.scenario.startRotation
)
}
@Presubmit
@Test
- fun primaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(primaryApp)
+ fun primaryAppWindowBecomesVisible() = flicker.appWindowBecomesVisible(primaryApp)
@Presubmit
@Test
- fun secondaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(secondaryApp)
+ fun secondaryAppWindowBecomesVisible() = flicker.appWindowBecomesVisible(secondaryApp)
@Presubmit
@Test
- fun thirdAppWindowBecomesVisible() = testSpec.appWindowBecomesInvisible(thirdApp)
+ fun thirdAppWindowBecomesVisible() = flicker.appWindowBecomesInvisible(thirdApp)
@Presubmit
@Test
- fun fourthAppWindowBecomesVisible() = testSpec.appWindowBecomesInvisible(fourthApp)
+ fun fourthAppWindowBecomesVisible() = flicker.appWindowBecomesInvisible(fourthApp)
/** {@inheritDoc} */
@FlakyTest(bugId = 251268711)
@Test
- override fun entireScreenCovered() =
- super.entireScreenCovered()
+ override fun entireScreenCovered() = super.entireScreenCovered()
/** {@inheritDoc} */
@Presubmit
@Test
- override fun navBarLayerIsVisibleAtStartAndEnd() =
- super.navBarLayerIsVisibleAtStartAndEnd()
+ override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
/** {@inheritDoc} */
@FlakyTest(bugId = 206753786)
@Test
- override fun navBarLayerPositionAtStartAndEnd() =
- super.navBarLayerPositionAtStartAndEnd()
+ override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
/** {@inheritDoc} */
@Presubmit
@Test
- override fun navBarWindowIsAlwaysVisible() =
- super.navBarWindowIsAlwaysVisible()
+ override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
/** {@inheritDoc} */
@Presubmit
@@ -206,26 +202,22 @@ class SwitchBetweenSplitPairs(testSpec: FlickerTestParameter) : SplitScreenBase(
/** {@inheritDoc} */
@Presubmit
@Test
- override fun statusBarLayerPositionAtStartAndEnd() =
- super.statusBarLayerPositionAtStartAndEnd()
+ override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd()
/** {@inheritDoc} */
@Presubmit
@Test
- override fun statusBarWindowIsAlwaysVisible() =
- super.statusBarWindowIsAlwaysVisible()
+ override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible()
/** {@inheritDoc} */
@Presubmit
@Test
- override fun taskBarLayerIsVisibleAtStartAndEnd() =
- super.taskBarLayerIsVisibleAtStartAndEnd()
+ override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
/** {@inheritDoc} */
@Presubmit
@Test
- override fun taskBarWindowIsAlwaysVisible() =
- super.taskBarWindowIsAlwaysVisible()
+ override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible()
/** {@inheritDoc} */
@FlakyTest
@@ -242,8 +234,8 @@ class SwitchBetweenSplitPairs(testSpec: FlickerTestParameter) : SplitScreenBase(
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests()
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests()
}
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index bee9a90ec6b8..8a5b4901ed96 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -53,6 +53,7 @@ import android.view.MotionEvent;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import android.window.BackEvent;
+import android.window.BackMotionEvent;
import android.window.BackNavigationInfo;
import android.window.IBackAnimationFinishedCallback;
import android.window.IOnBackInvokedCallback;
@@ -223,9 +224,10 @@ public class BackAnimationControllerTest extends ShellTestCase {
simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME);
- verify(mAnimatorCallback).onBackStarted(any(BackEvent.class));
+ verify(mAnimatorCallback).onBackStarted(any(BackMotionEvent.class));
verify(mBackAnimationRunner).onAnimationStart(anyInt(), any(), any(), any(), any());
- ArgumentCaptor<BackEvent> backEventCaptor = ArgumentCaptor.forClass(BackEvent.class);
+ ArgumentCaptor<BackMotionEvent> backEventCaptor =
+ ArgumentCaptor.forClass(BackMotionEvent.class);
verify(mAnimatorCallback, atLeastOnce()).onBackProgressed(backEventCaptor.capture());
// Check that back invocation is dispatched.
@@ -246,7 +248,8 @@ public class BackAnimationControllerTest extends ShellTestCase {
shellInit.init();
registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME);
- ArgumentCaptor<BackEvent> backEventCaptor = ArgumentCaptor.forClass(BackEvent.class);
+ ArgumentCaptor<BackMotionEvent> backEventCaptor =
+ ArgumentCaptor.forClass(BackMotionEvent.class);
createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, false);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java
index 3aefc3f03a8a..ba9c159bad28 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java
@@ -19,6 +19,7 @@ package com.android.wm.shell.back;
import static org.junit.Assert.assertEquals;
import android.window.BackEvent;
+import android.window.BackMotionEvent;
import org.junit.Before;
import org.junit.Test;
@@ -38,7 +39,7 @@ public class TouchTrackerTest {
@Test
public void generatesProgress_onStart() {
mTouchTracker.setGestureStartLocation(INITIAL_X_LEFT_EDGE, 0, BackEvent.EDGE_LEFT);
- BackEvent event = mTouchTracker.createStartEvent(null);
+ BackMotionEvent event = mTouchTracker.createStartEvent(null);
assertEquals(event.getProgress(), 0f, 0f);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
index 89bafcb6b2f4..b3c9e238a614 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
@@ -36,7 +36,7 @@ import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -50,6 +50,7 @@ import android.window.TransitionRequestInfo;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import android.window.WindowContainerTransaction.Change;
+import android.window.WindowContainerTransaction.HierarchyOp;
import androidx.test.filters.SmallTest;
@@ -99,15 +100,14 @@ public class DesktopModeControllerTest extends ShellTestCase {
@Before
public void setUp() {
mMockitoSession = mockitoSession().mockStatic(DesktopModeStatus.class).startMocking();
+ when(DesktopModeStatus.isProto1Enabled()).thenReturn(true);
when(DesktopModeStatus.isActive(any())).thenReturn(true);
mShellInit = Mockito.spy(new ShellInit(mTestExecutor));
mDesktopModeTaskRepository = new DesktopModeTaskRepository();
- mController = new DesktopModeController(mContext, mShellInit, mShellController,
- mShellTaskOrganizer, mRootTaskDisplayAreaOrganizer, mTransitions,
- mDesktopModeTaskRepository, mMockHandler, new TestShellExecutor());
+ mController = createController();
when(mShellTaskOrganizer.getRunningTasks(anyInt())).thenReturn(new ArrayList<>());
@@ -124,7 +124,17 @@ public class DesktopModeControllerTest extends ShellTestCase {
@Test
public void instantiate_addInitCallback() {
- verify(mShellInit, times(1)).addInitCallback(any(), any());
+ verify(mShellInit).addInitCallback(any(), any());
+ }
+
+ @Test
+ public void instantiate_flagOff_doNotAddInitCallback() {
+ when(DesktopModeStatus.isProto1Enabled()).thenReturn(false);
+ clearInvocations(mShellInit);
+
+ createController();
+
+ verify(mShellInit, never()).addInitCallback(any(), any());
}
@Test
@@ -222,25 +232,29 @@ public class DesktopModeControllerTest extends ShellTestCase {
// Check that there are hierarchy changes for home task and visible task
assertThat(wct.getHierarchyOps()).hasSize(2);
// First show home task
- WindowContainerTransaction.HierarchyOp op1 = wct.getHierarchyOps().get(0);
+ HierarchyOp op1 = wct.getHierarchyOps().get(0);
assertThat(op1.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
assertThat(op1.getContainer()).isEqualTo(homeTask.token.asBinder());
// Then visible task on top of it
- WindowContainerTransaction.HierarchyOp op2 = wct.getHierarchyOps().get(1);
+ HierarchyOp op2 = wct.getHierarchyOps().get(1);
assertThat(op2.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
assertThat(op2.getContainer()).isEqualTo(fullscreenTask1.token.asBinder());
}
@Test
- public void testShowDesktopApps() {
- // Set up two active tasks on desktop
+ public void testShowDesktopApps_allAppsInvisible_bringsToFront() {
+ // Set up two active tasks on desktop, task2 is on top of task1.
RunningTaskInfo freeformTask1 = createFreeformTask();
- freeformTask1.lastActiveTime = 100;
- RunningTaskInfo freeformTask2 = createFreeformTask();
- freeformTask2.lastActiveTime = 200;
mDesktopModeTaskRepository.addActiveTask(freeformTask1.taskId);
+ mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(freeformTask1.taskId);
+ mDesktopModeTaskRepository.updateVisibleFreeformTasks(
+ freeformTask1.taskId, false /* visible */);
+ RunningTaskInfo freeformTask2 = createFreeformTask();
mDesktopModeTaskRepository.addActiveTask(freeformTask2.taskId);
+ mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(freeformTask2.taskId);
+ mDesktopModeTaskRepository.updateVisibleFreeformTasks(
+ freeformTask2.taskId, false /* visible */);
when(mShellTaskOrganizer.getRunningTaskInfo(freeformTask1.taskId)).thenReturn(
freeformTask1);
when(mShellTaskOrganizer.getRunningTaskInfo(freeformTask2.taskId)).thenReturn(
@@ -248,27 +262,66 @@ public class DesktopModeControllerTest extends ShellTestCase {
// Run show desktop apps logic
mController.showDesktopApps();
- ArgumentCaptor<WindowContainerTransaction> wctCaptor = ArgumentCaptor.forClass(
- WindowContainerTransaction.class);
- if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- verify(mTransitions).startTransition(eq(TRANSIT_TO_FRONT), wctCaptor.capture(), any());
- } else {
- verify(mShellTaskOrganizer).applyTransaction(wctCaptor.capture());
- }
- WindowContainerTransaction wct = wctCaptor.getValue();
+ final WindowContainerTransaction wct = getBringAppsToFrontTransaction();
// Check wct has reorder calls
assertThat(wct.getHierarchyOps()).hasSize(2);
- // Task 2 has activity later, must be first
- WindowContainerTransaction.HierarchyOp op1 = wct.getHierarchyOps().get(0);
+ // Task 1 appeared first, must be first reorder to top.
+ HierarchyOp op1 = wct.getHierarchyOps().get(0);
assertThat(op1.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
- assertThat(op1.getContainer()).isEqualTo(freeformTask2.token.asBinder());
+ assertThat(op1.getContainer()).isEqualTo(freeformTask1.token.asBinder());
- // Task 1 should be second
- WindowContainerTransaction.HierarchyOp op2 = wct.getHierarchyOps().get(1);
+ // Task 2 appeared last, must be last reorder to top.
+ HierarchyOp op2 = wct.getHierarchyOps().get(1);
assertThat(op2.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
- assertThat(op2.getContainer()).isEqualTo(freeformTask1.token.asBinder());
+ assertThat(op2.getContainer()).isEqualTo(freeformTask2.token.asBinder());
+ }
+
+ @Test
+ public void testShowDesktopApps_appsAlreadyVisible_doesNothing() {
+ final RunningTaskInfo task1 = createFreeformTask();
+ mDesktopModeTaskRepository.addActiveTask(task1.taskId);
+ mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task1.taskId);
+ mDesktopModeTaskRepository.updateVisibleFreeformTasks(task1.taskId, true /* visible */);
+ when(mShellTaskOrganizer.getRunningTaskInfo(task1.taskId)).thenReturn(task1);
+ final RunningTaskInfo task2 = createFreeformTask();
+ mDesktopModeTaskRepository.addActiveTask(task2.taskId);
+ mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task2.taskId);
+ mDesktopModeTaskRepository.updateVisibleFreeformTasks(task2.taskId, true /* visible */);
+ when(mShellTaskOrganizer.getRunningTaskInfo(task2.taskId)).thenReturn(task2);
+
+ mController.showDesktopApps();
+
+ final WindowContainerTransaction wct = getBringAppsToFrontTransaction();
+ // No reordering needed.
+ assertThat(wct.getHierarchyOps()).isEmpty();
+ }
+
+ @Test
+ public void testShowDesktopApps_someAppsInvisible_reordersAll() {
+ final RunningTaskInfo task1 = createFreeformTask();
+ mDesktopModeTaskRepository.addActiveTask(task1.taskId);
+ mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task1.taskId);
+ mDesktopModeTaskRepository.updateVisibleFreeformTasks(task1.taskId, false /* visible */);
+ when(mShellTaskOrganizer.getRunningTaskInfo(task1.taskId)).thenReturn(task1);
+ final RunningTaskInfo task2 = createFreeformTask();
+ mDesktopModeTaskRepository.addActiveTask(task2.taskId);
+ mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task2.taskId);
+ mDesktopModeTaskRepository.updateVisibleFreeformTasks(task2.taskId, true /* visible */);
+ when(mShellTaskOrganizer.getRunningTaskInfo(task2.taskId)).thenReturn(task2);
+
+ mController.showDesktopApps();
+
+ final WindowContainerTransaction wct = getBringAppsToFrontTransaction();
+ // Both tasks should be reordered to top, even if one was already visible.
+ assertThat(wct.getHierarchyOps()).hasSize(2);
+ final HierarchyOp op1 = wct.getHierarchyOps().get(0);
+ assertThat(op1.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
+ assertThat(op1.getContainer()).isEqualTo(task1.token.asBinder());
+ final HierarchyOp op2 = wct.getHierarchyOps().get(1);
+ assertThat(op2.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
+ assertThat(op2.getContainer()).isEqualTo(task2.token.asBinder());
}
@Test
@@ -309,6 +362,12 @@ public class DesktopModeControllerTest extends ShellTestCase {
assertThat(wct).isNotNull();
}
+ private DesktopModeController createController() {
+ return new DesktopModeController(mContext, mShellInit, mShellController,
+ mShellTaskOrganizer, mRootTaskDisplayAreaOrganizer, mTransitions,
+ mDesktopModeTaskRepository, mMockHandler, new TestShellExecutor());
+ }
+
private DisplayAreaInfo createMockDisplayArea() {
DisplayAreaInfo displayAreaInfo = new DisplayAreaInfo(new MockToken().mToken,
mContext.getDisplayId(), 0);
@@ -355,6 +414,17 @@ public class DesktopModeControllerTest extends ShellTestCase {
return arg.getValue();
}
+ private WindowContainerTransaction getBringAppsToFrontTransaction() {
+ final ArgumentCaptor<WindowContainerTransaction> arg = ArgumentCaptor.forClass(
+ WindowContainerTransaction.class);
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ verify(mTransitions).startTransition(eq(TRANSIT_TO_FRONT), arg.capture(), any());
+ } else {
+ verify(mShellTaskOrganizer).applyTransaction(arg.capture());
+ }
+ return arg.getValue();
+ }
+
private void assertThatBoundsCleared(Change change) {
assertThat((change.getWindowSetMask() & WINDOW_CONFIG_BOUNDS) != 0).isTrue();
assertThat(change.getConfiguration().windowConfiguration.getBounds().isEmpty()).isTrue();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
index aaa5c8a35acb..1e43a5983821 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
@@ -140,6 +140,32 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() {
assertThat(listener.visibleFreeformTaskChangedCalls).isEqualTo(3)
}
+ @Test
+ fun addOrMoveFreeformTaskToTop_didNotExist_addsToTop() {
+ repo.addOrMoveFreeformTaskToTop(5)
+ repo.addOrMoveFreeformTaskToTop(6)
+ repo.addOrMoveFreeformTaskToTop(7)
+
+ val tasks = repo.getFreeformTasksInZOrder()
+ assertThat(tasks.size).isEqualTo(3)
+ assertThat(tasks[0]).isEqualTo(7)
+ assertThat(tasks[1]).isEqualTo(6)
+ assertThat(tasks[2]).isEqualTo(5)
+ }
+
+ @Test
+ fun addOrMoveFreeformTaskToTop_alreadyExists_movesToTop() {
+ repo.addOrMoveFreeformTaskToTop(5)
+ repo.addOrMoveFreeformTaskToTop(6)
+ repo.addOrMoveFreeformTaskToTop(7)
+
+ repo.addOrMoveFreeformTaskToTop(6)
+
+ val tasks = repo.getFreeformTasksInZOrder()
+ assertThat(tasks.size).isEqualTo(3)
+ assertThat(tasks.first()).isEqualTo(6)
+ }
+
class TestListener : DesktopModeTaskRepository.ActiveTasksListener {
var activeTaskChangedCalls = 0
override fun onActiveTasksChanged() {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/OWNERS b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/OWNERS
new file mode 100644
index 000000000000..736d4cff6ce8
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/OWNERS
@@ -0,0 +1,3 @@
+# WM shell sub-module TV pip owners
+galinap@google.com
+bronger@google.com \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
index 38b75f81171f..f8ded7709c68 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
@@ -178,10 +178,10 @@ public class SplitScreenControllerTests extends ShellTestCase {
Intent startIntent = createStartIntent("startActivity");
PendingIntent pendingIntent =
PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE);
- // Put the same component into focus task
- ActivityManager.RunningTaskInfo focusTaskInfo =
+ // Put the same component to the top running task
+ ActivityManager.RunningTaskInfo topRunningTask =
createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, startIntent);
- doReturn(focusTaskInfo).when(mStageCoordinator).getFocusingTaskInfo();
+ doReturn(topRunningTask).when(mRecentTasks).getTopRunningTask();
doReturn(true).when(mStageCoordinator).isValidToEnterSplitScreen(any());
mSplitScreenController.startIntent(pendingIntent, null, SPLIT_POSITION_TOP_OR_LEFT, null);
@@ -199,10 +199,10 @@ public class SplitScreenControllerTests extends ShellTestCase {
Intent startIntent = createStartIntent("startActivity");
PendingIntent pendingIntent =
PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE);
- // Put the same component into focus task
- ActivityManager.RunningTaskInfo focusTaskInfo =
+ // Put the same component to the top running task
+ ActivityManager.RunningTaskInfo topRunningTask =
createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, startIntent);
- doReturn(focusTaskInfo).when(mStageCoordinator).getFocusingTaskInfo();
+ doReturn(topRunningTask).when(mRecentTasks).getTopRunningTask();
doReturn(true).when(mStageCoordinator).isValidToEnterSplitScreen(any());
// Put the same component into a task in the background
ActivityManager.RecentTaskInfo sameTaskInfo = new ActivityManager.RecentTaskInfo();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModelTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModelTests.java
index 8b134ed1dfe4..ad6fcedd3166 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModelTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModelTests.java
@@ -54,6 +54,7 @@ import org.mockito.Mock;
import java.util.ArrayList;
import java.util.List;
+import java.util.Optional;
import java.util.function.Supplier;
/** Tests of {@link CaptionWindowDecorViewModel} */
@@ -101,7 +102,7 @@ public class CaptionWindowDecorViewModelTests extends ShellTestCase {
mTaskOrganizer,
mDisplayController,
mSyncQueue,
- mDesktopModeController,
+ Optional.of(mDesktopModeController),
mCaptionWindowDecorFactory,
new MockObjectSupplier<>(mMockInputManagers, () -> mock(InputManager.class)));
mCaptionWindowDecorViewModel.setEventReceiverFactory(mEventReceiverFactory);
@@ -111,7 +112,7 @@ public class CaptionWindowDecorViewModelTests extends ShellTestCase {
.create(any(), any(), any(), any(), any(), any(), any(), any());
when(mInputManager.monitorGestureInput(any(), anyInt())).thenReturn(mInputMonitor);
- when(mEventReceiverFactory.create(any(), any())).thenReturn(mEventReceiver);
+ when(mEventReceiverFactory.create(any(), any(), any())).thenReturn(mEventReceiver);
when(mInputMonitor.getInputChannel()).thenReturn(mInputChannel);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index 15181b1549f5..dd9ab9899e13 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -113,6 +113,12 @@ public class WindowDecorationTests extends ShellTestCase {
mMockSurfaceControlFinishT = createMockSurfaceControlTransaction();
mMockSurfaceControlAddWindowT = createMockSurfaceControlTransaction();
+ mRelayoutParams.mLayoutResId = 0;
+ mRelayoutParams.mCaptionHeightId = R.dimen.test_freeform_decor_caption_height;
+ // Caption should have fixed width except in testLayoutResultCalculation_fullWidthCaption()
+ mRelayoutParams.mCaptionWidthId = R.dimen.test_freeform_decor_caption_width;
+ mRelayoutParams.mShadowRadiusId = R.dimen.test_window_decor_shadow_radius;
+
doReturn(mMockSurfaceControlViewHost).when(mMockSurfaceControlViewHostFactory)
.create(any(), any(), any());
}
@@ -435,6 +441,58 @@ public class WindowDecorationTests extends ShellTestCase {
assertThat(additionalWindow.mWindowSurface).isNull();
}
+ @Test
+ public void testLayoutResultCalculation_fullWidthCaption() {
+ final Display defaultDisplay = mock(Display.class);
+ doReturn(defaultDisplay).when(mMockDisplayController)
+ .getDisplay(Display.DEFAULT_DISPLAY);
+
+ final SurfaceControl decorContainerSurface = mock(SurfaceControl.class);
+ final SurfaceControl.Builder decorContainerSurfaceBuilder =
+ createMockSurfaceControlBuilder(decorContainerSurface);
+ mMockSurfaceControlBuilders.add(decorContainerSurfaceBuilder);
+ final SurfaceControl taskBackgroundSurface = mock(SurfaceControl.class);
+ final SurfaceControl.Builder taskBackgroundSurfaceBuilder =
+ createMockSurfaceControlBuilder(taskBackgroundSurface);
+ mMockSurfaceControlBuilders.add(taskBackgroundSurfaceBuilder);
+ final SurfaceControl captionContainerSurface = mock(SurfaceControl.class);
+ final SurfaceControl.Builder captionContainerSurfaceBuilder =
+ createMockSurfaceControlBuilder(captionContainerSurface);
+ mMockSurfaceControlBuilders.add(captionContainerSurfaceBuilder);
+
+ final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
+ mMockSurfaceControlTransactions.add(t);
+ final ActivityManager.TaskDescription.Builder taskDescriptionBuilder =
+ new ActivityManager.TaskDescription.Builder()
+ .setBackgroundColor(Color.YELLOW);
+ final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
+ .setDisplayId(Display.DEFAULT_DISPLAY)
+ .setTaskDescriptionBuilder(taskDescriptionBuilder)
+ .setBounds(TASK_BOUNDS)
+ .setPositionInParent(TASK_POSITION_IN_PARENT.x, TASK_POSITION_IN_PARENT.y)
+ .setVisible(true)
+ .build();
+ taskInfo.isFocused = true;
+ taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
+ mRelayoutParams.setOutsets(
+ R.dimen.test_window_decor_left_outset,
+ R.dimen.test_window_decor_top_outset,
+ R.dimen.test_window_decor_right_outset,
+ R.dimen.test_window_decor_bottom_outset);
+ final SurfaceControl taskSurface = mock(SurfaceControl.class);
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
+
+ mRelayoutParams.mCaptionWidthId = Resources.ID_NULL;
+ windowDecor.relayout(taskInfo);
+
+ verify(captionContainerSurfaceBuilder).setParent(decorContainerSurface);
+ verify(captionContainerSurfaceBuilder).setContainerLayer();
+ verify(mMockSurfaceControlStartT).setPosition(captionContainerSurface, 20, 40);
+ // Width of the captionContainerSurface should match the width of TASK_BOUNDS
+ verify(mMockSurfaceControlStartT).setWindowCrop(captionContainerSurface, 300, 64);
+ verify(mMockSurfaceControlStartT).show(captionContainerSurface);
+ }
+
private TestWindowDecoration createWindowDecoration(
ActivityManager.RunningTaskInfo taskInfo, SurfaceControl testSurface) {
return new TestWindowDecoration(InstrumentationRegistry.getInstrumentation().getContext(),
@@ -490,11 +548,6 @@ public class WindowDecorationTests extends ShellTestCase {
@Override
void relayout(ActivityManager.RunningTaskInfo taskInfo) {
- mRelayoutParams.mLayoutResId = 0;
- mRelayoutParams.mCaptionHeightId = R.dimen.test_freeform_decor_caption_height;
- mRelayoutParams.mCaptionWidthId = R.dimen.test_freeform_decor_caption_width;
- mRelayoutParams.mShadowRadiusId = R.dimen.test_window_decor_shadow_radius;
-
relayout(mRelayoutParams, mMockSurfaceControlStartT, mMockSurfaceControlFinishT,
mMockWindowContainerTransaction, mMockView, mRelayoutResult);
}
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 88cfed9357d8..3e3d77b89a9a 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -340,6 +340,8 @@ cc_defaults {
"jni/Graphics.cpp",
"jni/ImageDecoder.cpp",
"jni/Interpolator.cpp",
+ "jni/MeshSpecification.cpp",
+ "jni/Mesh.cpp",
"jni/MaskFilter.cpp",
"jni/NinePatch.cpp",
"jni/NinePatchPeeker.cpp",
@@ -570,6 +572,7 @@ cc_defaults {
"renderthread/VulkanSurface.cpp",
"renderthread/RenderProxy.cpp",
"renderthread/RenderThread.cpp",
+ "renderthread/HintSessionWrapper.cpp",
"service/GraphicsStatsService.cpp",
"thread/CommonPool.cpp",
"utils/GLUtils.cpp",
diff --git a/libs/hwui/DisplayListOps.in b/libs/hwui/DisplayListOps.in
index 4ec782f6fec0..e2127efca716 100644
--- a/libs/hwui/DisplayListOps.in
+++ b/libs/hwui/DisplayListOps.in
@@ -52,3 +52,4 @@ X(DrawShadowRec)
X(DrawVectorDrawable)
X(DrawRippleDrawable)
X(DrawWebView)
+X(DrawMesh)
diff --git a/libs/hwui/FrameInfo.h b/libs/hwui/FrameInfo.h
index 564ee4f53a54..b15b6cb9a9ec 100644
--- a/libs/hwui/FrameInfo.h
+++ b/libs/hwui/FrameInfo.h
@@ -104,6 +104,7 @@ public:
set(FrameInfoIndex::AnimationStart) = vsyncTime;
set(FrameInfoIndex::PerformTraversalsStart) = vsyncTime;
set(FrameInfoIndex::DrawStart) = vsyncTime;
+ set(FrameInfoIndex::FrameStartTime) = vsyncTime;
set(FrameInfoIndex::FrameDeadline) = frameDeadline;
set(FrameInfoIndex::FrameInterval) = frameInterval;
return *this;
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index f070e97dff2a..3f21940d35a7 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -15,11 +15,13 @@
*/
#include "RecordingCanvas.h"
-#include <hwui/Paint.h>
#include <GrRecordingContext.h>
+#include <SkMesh.h>
+#include <hwui/Paint.h>
#include <experimental/type_traits>
+#include <utility>
#include "SkAndroidFrameworkUtils.h"
#include "SkBlendMode.h"
@@ -271,7 +273,6 @@ struct DrawDRRect final : Op {
SkPaint paint;
void draw(SkCanvas* c, const SkMatrix&) const { c->drawDRRect(outer, inner, paint); }
};
-
struct DrawAnnotation final : Op {
static const auto kType = Type::DrawAnnotation;
DrawAnnotation(const SkRect& rect, SkData* value) : rect(rect), value(sk_ref_sp(value)) {}
@@ -453,6 +454,16 @@ struct DrawVertices final : Op {
c->drawVertices(vertices, mode, paint);
}
};
+struct DrawMesh final : Op {
+ static const auto kType = Type::DrawMesh;
+ DrawMesh(const SkMesh& mesh, sk_sp<SkBlender> blender, const SkPaint& paint)
+ : mesh(mesh), blender(std::move(blender)), paint(paint) {}
+
+ SkMesh mesh;
+ sk_sp<SkBlender> blender;
+ SkPaint paint;
+ void draw(SkCanvas* c, const SkMatrix&) const { c->drawMesh(mesh, blender, paint); }
+};
struct DrawAtlas final : Op {
static const auto kType = Type::DrawAtlas;
DrawAtlas(const SkImage* atlas, int count, SkBlendMode mode, const SkSamplingOptions& sampling,
@@ -764,6 +775,10 @@ void DisplayListData::drawPoints(SkCanvas::PointMode mode, size_t count, const S
void DisplayListData::drawVertices(const SkVertices* vert, SkBlendMode mode, const SkPaint& paint) {
this->push<DrawVertices>(0, vert, mode, paint);
}
+void DisplayListData::drawMesh(const SkMesh& mesh, const sk_sp<SkBlender>& blender,
+ const SkPaint& paint) {
+ this->push<DrawMesh>(0, mesh, blender, paint);
+}
void DisplayListData::drawAtlas(const SkImage* atlas, const SkRSXform xforms[], const SkRect texs[],
const SkColor colors[], int count, SkBlendMode xfermode,
const SkSamplingOptions& sampling, const SkRect* cull,
@@ -1106,6 +1121,10 @@ void RecordingCanvas::onDrawVerticesObject(const SkVertices* vertices,
SkBlendMode mode, const SkPaint& paint) {
fDL->drawVertices(vertices, mode, paint);
}
+void RecordingCanvas::onDrawMesh(const SkMesh& mesh, sk_sp<SkBlender> blender,
+ const SkPaint& paint) {
+ fDL->drawMesh(mesh, blender, paint);
+}
void RecordingCanvas::onDrawAtlas2(const SkImage* atlas, const SkRSXform xforms[],
const SkRect texs[], const SkColor colors[], int count,
SkBlendMode bmode, const SkSamplingOptions& sampling,
diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h
index f37729ebb59c..2539694a73ee 100644
--- a/libs/hwui/RecordingCanvas.h
+++ b/libs/hwui/RecordingCanvas.h
@@ -112,6 +112,8 @@ private:
void drawRRect(const SkRRect&, const SkPaint&);
void drawDRRect(const SkRRect&, const SkRRect&, const SkPaint&);
+ void drawMesh(const SkMesh&, const sk_sp<SkBlender>&, const SkPaint&);
+
void drawAnnotation(const SkRect&, const char*, SkData*);
void drawDrawable(SkDrawable*, const SkMatrix*);
void drawPicture(const SkPicture*, const SkMatrix*, const SkPaint*);
@@ -211,6 +213,7 @@ public:
const SkPaint&) override;
void onDrawPoints(PointMode, size_t count, const SkPoint pts[], const SkPaint&) override;
void onDrawVerticesObject(const SkVertices*, SkBlendMode, const SkPaint&) override;
+ void onDrawMesh(const SkMesh&, sk_sp<SkBlender>, const SkPaint&) override;
void onDrawAtlas2(const SkImage*, const SkRSXform[], const SkRect[], const SkColor[], int,
SkBlendMode, const SkSamplingOptions&, const SkRect*, const SkPaint*) override;
void onDrawShadowRec(const SkPath&, const SkDrawShadowRec&) override;
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index 473afbd2aa2f..d83d78f650aa 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -570,6 +570,10 @@ void SkiaCanvas::drawVertices(const SkVertices* vertices, SkBlendMode mode, cons
applyLooper(&paint, [&](const SkPaint& p) { mCanvas->drawVertices(vertices, mode, p); });
}
+void SkiaCanvas::drawMesh(const SkMesh& mesh, sk_sp<SkBlender> blender, const SkPaint& paint) {
+ mCanvas->drawMesh(mesh, blender, paint);
+}
+
// ----------------------------------------------------------------------------
// Canvas draw operations: Bitmaps
// ----------------------------------------------------------------------------
diff --git a/libs/hwui/SkiaCanvas.h b/libs/hwui/SkiaCanvas.h
index eece77e061b6..31e3b4c3c7e2 100644
--- a/libs/hwui/SkiaCanvas.h
+++ b/libs/hwui/SkiaCanvas.h
@@ -120,8 +120,8 @@ public:
virtual void drawRoundRect(float left, float top, float right, float bottom, float rx, float ry,
const Paint& paint) override;
- virtual void drawDoubleRoundRect(const SkRRect& outer, const SkRRect& inner,
- const Paint& paint) override;
+ virtual void drawDoubleRoundRect(const SkRRect& outer, const SkRRect& inner,
+ const Paint& paint) override;
virtual void drawCircle(float x, float y, float radius, const Paint& paint) override;
virtual void drawOval(float left, float top, float right, float bottom,
@@ -130,6 +130,8 @@ public:
float sweepAngle, bool useCenter, const Paint& paint) override;
virtual void drawPath(const SkPath& path, const Paint& paint) override;
virtual void drawVertices(const SkVertices*, SkBlendMode, const Paint& paint) override;
+ virtual void drawMesh(const SkMesh& mesh, sk_sp<SkBlender> blender,
+ const SkPaint& paint) override;
virtual void drawBitmap(Bitmap& bitmap, float left, float top, const Paint* paint) override;
virtual void drawBitmap(Bitmap& bitmap, const SkMatrix& matrix, const Paint* paint) override;
diff --git a/libs/hwui/apex/jni_runtime.cpp b/libs/hwui/apex/jni_runtime.cpp
index 39725a55b594..e6cfa7bcaf70 100644
--- a/libs/hwui/apex/jni_runtime.cpp
+++ b/libs/hwui/apex/jni_runtime.cpp
@@ -76,6 +76,8 @@ extern int register_android_graphics_pdf_PdfRenderer(JNIEnv* env);
extern int register_android_graphics_text_MeasuredText(JNIEnv* env);
extern int register_android_graphics_text_LineBreaker(JNIEnv *env);
extern int register_android_graphics_text_TextShaper(JNIEnv *env);
+extern int register_android_graphics_MeshSpecification(JNIEnv* env);
+extern int register_android_graphics_Mesh(JNIEnv* env);
extern int register_android_util_PathParser(JNIEnv* env);
extern int register_android_view_DisplayListCanvas(JNIEnv* env);
@@ -143,6 +145,8 @@ extern int register_android_view_ThreadedRenderer(JNIEnv* env);
REG_JNI(register_android_graphics_text_MeasuredText),
REG_JNI(register_android_graphics_text_LineBreaker),
REG_JNI(register_android_graphics_text_TextShaper),
+ REG_JNI(register_android_graphics_MeshSpecification),
+ REG_JNI(register_android_graphics_Mesh),
REG_JNI(register_android_util_PathParser),
REG_JNI(register_android_view_RenderNode),
diff --git a/libs/hwui/hwui/BlurDrawLooper.cpp b/libs/hwui/hwui/BlurDrawLooper.cpp
index d4b0198d015d..8b52551fc107 100644
--- a/libs/hwui/hwui/BlurDrawLooper.cpp
+++ b/libs/hwui/hwui/BlurDrawLooper.cpp
@@ -15,6 +15,7 @@
*/
#include "BlurDrawLooper.h"
+#include <SkBlurTypes.h>
#include <SkColorSpace.h>
#include <SkMaskFilter.h>
diff --git a/libs/hwui/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h
index 4608088a7cd9..2a2019199bda 100644
--- a/libs/hwui/hwui/Canvas.h
+++ b/libs/hwui/hwui/Canvas.h
@@ -227,6 +227,7 @@ public:
float sweepAngle, bool useCenter, const Paint& paint) = 0;
virtual void drawPath(const SkPath& path, const Paint& paint) = 0;
virtual void drawVertices(const SkVertices*, SkBlendMode, const Paint& paint) = 0;
+ virtual void drawMesh(const SkMesh& mesh, sk_sp<SkBlender>, const SkPaint& paint) = 0;
// Bitmap-based
virtual void drawBitmap(Bitmap& bitmap, float left, float top, const Paint* paint) = 0;
diff --git a/libs/hwui/jni/MaskFilter.cpp b/libs/hwui/jni/MaskFilter.cpp
index 5383032e0f77..048ce025ce27 100644
--- a/libs/hwui/jni/MaskFilter.cpp
+++ b/libs/hwui/jni/MaskFilter.cpp
@@ -2,6 +2,7 @@
#include "SkMaskFilter.h"
#include "SkBlurMask.h"
#include "SkBlurMaskFilter.h"
+#include "SkBlurTypes.h"
#include "SkTableMaskFilter.h"
static void ThrowIAE_IfNull(JNIEnv* env, void* ptr) {
diff --git a/libs/hwui/jni/Mesh.cpp b/libs/hwui/jni/Mesh.cpp
index 6dba6c1a966b..7c732d7fdcf2 100644
--- a/libs/hwui/jni/Mesh.cpp
+++ b/libs/hwui/jni/Mesh.cpp
@@ -37,15 +37,25 @@ sk_sp<SkMesh::IndexBuffer> genIndexBuffer(JNIEnv* env, jobject buffer, int size,
return indexBuffer;
}
+// TODO(b/260252882): undefine SK_LEGACY_MESH_MAKE and remove this.
+template <typename T>
+SkMesh get_mesh_from_result(T&& result) {
+#ifdef SK_LEGACY_MESH_MAKE
+ return result;
+#else
+ return result.mesh;
+#endif
+}
+
static jlong make(JNIEnv* env, jobject, jlong meshSpec, jint mode, jobject vertexBuffer,
jboolean isDirect, jint vertexCount, jint vertexOffset, jint left, jint top,
jint right, jint bottom) {
auto skMeshSpec = sk_ref_sp(reinterpret_cast<SkMeshSpecification*>(meshSpec));
sk_sp<SkMesh::VertexBuffer> skVertexBuffer =
- genVertexBuffer(env, vertexBuffer, skMeshSpec->attributes().size_bytes(), isDirect);
+ genVertexBuffer(env, vertexBuffer, vertexCount * skMeshSpec->stride(), isDirect);
auto skRect = SkRect::MakeLTRB(left, top, right, bottom);
- auto mesh = SkMesh::Make(skMeshSpec, SkMesh::Mode(mode), skVertexBuffer, vertexCount,
- vertexOffset, nullptr, skRect);
+ auto mesh = get_mesh_from_result(SkMesh::Make(skMeshSpec, SkMesh::Mode(mode), skVertexBuffer,
+ vertexCount, vertexOffset, nullptr, skRect));
auto meshPtr = std::make_unique<MeshWrapper>(MeshWrapper{mesh, MeshUniformBuilder(skMeshSpec)});
return reinterpret_cast<jlong>(meshPtr.release());
}
@@ -55,14 +65,14 @@ static jlong makeIndexed(JNIEnv* env, jobject, jlong meshSpec, jint mode, jobjec
jobject indexBuffer, jboolean isIndexDirect, jint indexCount,
jint indexOffset, jint left, jint top, jint right, jint bottom) {
auto skMeshSpec = sk_ref_sp(reinterpret_cast<SkMeshSpecification*>(meshSpec));
- sk_sp<SkMesh::VertexBuffer> skVertexBuffer = genVertexBuffer(
- env, vertexBuffer, skMeshSpec->attributes().size_bytes(), isVertexDirect);
+ sk_sp<SkMesh::VertexBuffer> skVertexBuffer =
+ genVertexBuffer(env, vertexBuffer, vertexCount * skMeshSpec->stride(), isVertexDirect);
sk_sp<SkMesh::IndexBuffer> skIndexBuffer =
genIndexBuffer(env, indexBuffer, indexCount * gIndexByteSize, isIndexDirect);
auto skRect = SkRect::MakeLTRB(left, top, right, bottom);
- auto mesh = SkMesh::MakeIndexed(skMeshSpec, SkMesh::Mode(mode), skVertexBuffer, vertexCount,
- vertexOffset, skIndexBuffer, indexCount, indexOffset, nullptr,
- skRect);
+ auto mesh = get_mesh_from_result(SkMesh::MakeIndexed(
+ skMeshSpec, SkMesh::Mode(mode), skVertexBuffer, vertexCount, vertexOffset,
+ skIndexBuffer, indexCount, indexOffset, nullptr, skRect));
auto meshPtr = std::make_unique<MeshWrapper>(MeshWrapper{mesh, MeshUniformBuilder(skMeshSpec)});
return reinterpret_cast<jlong>(meshPtr.release());
}
@@ -71,14 +81,15 @@ static void updateMesh(JNIEnv* env, jobject, jlong meshWrapper, jboolean indexed
auto wrapper = reinterpret_cast<MeshWrapper*>(meshWrapper);
auto mesh = wrapper->mesh;
if (indexed) {
- wrapper->mesh = SkMesh::MakeIndexed(
+ wrapper->mesh = get_mesh_from_result(SkMesh::MakeIndexed(
sk_ref_sp(mesh.spec()), mesh.mode(), sk_ref_sp(mesh.vertexBuffer()),
mesh.vertexCount(), mesh.vertexOffset(), sk_ref_sp(mesh.indexBuffer()),
- mesh.indexCount(), mesh.indexOffset(), wrapper->builder.fUniforms, mesh.bounds());
+ mesh.indexCount(), mesh.indexOffset(), wrapper->builder.fUniforms, mesh.bounds()));
} else {
- wrapper->mesh = SkMesh::Make(
- sk_ref_sp(mesh.spec()), mesh.mode(), sk_ref_sp(mesh.vertexBuffer()),
- mesh.vertexCount(), mesh.vertexOffset(), wrapper->builder.fUniforms, mesh.bounds());
+ wrapper->mesh = get_mesh_from_result(
+ SkMesh::Make(sk_ref_sp(mesh.spec()), mesh.mode(), sk_ref_sp(mesh.vertexBuffer()),
+ mesh.vertexCount(), mesh.vertexOffset(), wrapper->builder.fUniforms,
+ mesh.bounds()));
}
}
diff --git a/libs/hwui/jni/MeshSpecification.cpp b/libs/hwui/jni/MeshSpecification.cpp
index 22fa4d39e2ed..619a3ed552d8 100644
--- a/libs/hwui/jni/MeshSpecification.cpp
+++ b/libs/hwui/jni/MeshSpecification.cpp
@@ -50,7 +50,6 @@ std::vector<Attribute> extractAttributes(JNIEnv* env, jobjectArray attributes) {
SkString(attName.c_str())};
attVector.push_back(std::move(temp));
}
-
return attVector;
}
@@ -76,11 +75,15 @@ static jlong Make(JNIEnv* env, jobject thiz, jobjectArray attributeArray, jint v
auto varyings = extractVaryings(env, varyingArray);
auto skVertexShader = ScopedUtfChars(env, vertexShader);
auto skFragmentShader = ScopedUtfChars(env, fragmentShader);
- auto meshSpec = SkMeshSpecification::Make(attributes, vertexStride, varyings,
- SkString(skVertexShader.c_str()),
- SkString(skFragmentShader.c_str()))
- .specification;
- return reinterpret_cast<jlong>(meshSpec.release());
+ auto meshSpecResult = SkMeshSpecification::Make(attributes, vertexStride, varyings,
+ SkString(skVertexShader.c_str()),
+ SkString(skFragmentShader.c_str()));
+
+ if (meshSpecResult.specification.get() == nullptr) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", meshSpecResult.error.c_str());
+ }
+
+ return reinterpret_cast<jlong>(meshSpecResult.specification.release());
}
static jlong MakeWithCS(JNIEnv* env, jobject thiz, jobjectArray attributeArray, jint vertexStride,
@@ -90,13 +93,15 @@ static jlong MakeWithCS(JNIEnv* env, jobject thiz, jobjectArray attributeArray,
auto varyings = extractVaryings(env, varyingArray);
auto skVertexShader = ScopedUtfChars(env, vertexShader);
auto skFragmentShader = ScopedUtfChars(env, fragmentShader);
- auto meshSpec = SkMeshSpecification::Make(attributes, vertexStride, varyings,
- SkString(skVertexShader.c_str()),
- SkString(skFragmentShader.c_str()),
- GraphicsJNI::getNativeColorSpace(colorSpace))
- .specification;
+ auto meshSpecResult = SkMeshSpecification::Make(
+ attributes, vertexStride, varyings, SkString(skVertexShader.c_str()),
+ SkString(skFragmentShader.c_str()), GraphicsJNI::getNativeColorSpace(colorSpace));
- return reinterpret_cast<jlong>(meshSpec.release());
+ if (meshSpecResult.specification.get() == nullptr) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", meshSpecResult.error.c_str());
+ }
+
+ return reinterpret_cast<jlong>(meshSpecResult.specification.release());
}
static jlong MakeWithAlpha(JNIEnv* env, jobject thiz, jobjectArray attributeArray,
@@ -106,12 +111,16 @@ static jlong MakeWithAlpha(JNIEnv* env, jobject thiz, jobjectArray attributeArra
auto varyings = extractVaryings(env, varyingArray);
auto skVertexShader = ScopedUtfChars(env, vertexShader);
auto skFragmentShader = ScopedUtfChars(env, fragmentShader);
- auto meshSpec = SkMeshSpecification::Make(
- attributes, vertexStride, varyings, SkString(skVertexShader.c_str()),
- SkString(skFragmentShader.c_str()),
- GraphicsJNI::getNativeColorSpace(colorSpace), SkAlphaType(alphaType))
- .specification;
- return reinterpret_cast<jlong>(meshSpec.release());
+ auto meshSpecResult = SkMeshSpecification::Make(
+ attributes, vertexStride, varyings, SkString(skVertexShader.c_str()),
+ SkString(skFragmentShader.c_str()), GraphicsJNI::getNativeColorSpace(colorSpace),
+ SkAlphaType(alphaType));
+
+ if (meshSpecResult.specification.get() == nullptr) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", meshSpecResult.error.c_str());
+ }
+
+ return reinterpret_cast<jlong>(meshSpecResult.specification.release());
}
static void MeshSpecification_safeUnref(SkMeshSpecification* meshSpec) {
@@ -153,4 +162,4 @@ int register_android_graphics_MeshSpecification(JNIEnv* env) {
return 0;
}
-} // namespace android \ No newline at end of file
+} // namespace android
diff --git a/libs/hwui/jni/android_graphics_Canvas.cpp b/libs/hwui/jni/android_graphics_Canvas.cpp
index 0513447ed05e..8a4d4e17edb1 100644
--- a/libs/hwui/jni/android_graphics_Canvas.cpp
+++ b/libs/hwui/jni/android_graphics_Canvas.cpp
@@ -21,6 +21,7 @@
#else
#define __ANDROID_API_P__ 28
#endif
+#include <Mesh.h>
#include <androidfw/ResourceTypes.h>
#include <hwui/Canvas.h>
#include <hwui/Paint.h>
@@ -30,8 +31,8 @@
#include <nativehelper/ScopedPrimitiveArray.h>
#include <nativehelper/ScopedStringChars.h>
-#include "FontUtils.h"
#include "Bitmap.h"
+#include "FontUtils.h"
#include "SkBitmap.h"
#include "SkBlendMode.h"
#include "SkClipOp.h"
@@ -42,10 +43,10 @@
#include "SkMatrix.h"
#include "SkPath.h"
#include "SkPoint.h"
+#include "SkRRect.h"
#include "SkRect.h"
#include "SkRefCnt.h"
#include "SkRegion.h"
-#include "SkRRect.h"
#include "SkScalar.h"
#include "SkVertices.h"
@@ -443,6 +444,14 @@ static void drawVertices(JNIEnv* env, jobject, jlong canvasHandle,
blendMode, *paint);
}
+static void drawMesh(JNIEnv* env, jobject, jlong canvasHandle, jlong meshHandle, jint modeHandle,
+ jlong paintHandle) {
+ const SkMesh mesh = reinterpret_cast<MeshWrapper*>(meshHandle)->mesh;
+ SkBlendMode blendMode = static_cast<SkBlendMode>(modeHandle);
+ SkPaint* paint = reinterpret_cast<Paint*>(paintHandle);
+ get_canvas(canvasHandle)->drawMesh(mesh, SkBlender::Mode(blendMode), *paint);
+}
+
static void drawNinePatch(JNIEnv* env, jobject, jlong canvasHandle, jlong bitmapHandle,
jlong chunkHandle, jfloat left, jfloat top, jfloat right, jfloat bottom,
jlong paintHandle, jint dstDensity, jint srcDensity) {
@@ -761,38 +770,38 @@ static const JNINativeMethod gMethods[] = {
// If called from Canvas these are regular JNI
// If called from DisplayListCanvas they are @FastNative
static const JNINativeMethod gDrawMethods[] = {
- {"nDrawColor","(JII)V", (void*) CanvasJNI::drawColor},
- {"nDrawColor","(JJJI)V", (void*) CanvasJNI::drawColorLong},
- {"nDrawPaint","(JJ)V", (void*) CanvasJNI::drawPaint},
- {"nDrawPoint", "(JFFJ)V", (void*) CanvasJNI::drawPoint},
- {"nDrawPoints", "(J[FIIJ)V", (void*) CanvasJNI::drawPoints},
- {"nDrawLine", "(JFFFFJ)V", (void*) CanvasJNI::drawLine},
- {"nDrawLines", "(J[FIIJ)V", (void*) CanvasJNI::drawLines},
- {"nDrawRect","(JFFFFJ)V", (void*) CanvasJNI::drawRect},
- {"nDrawRegion", "(JJJ)V", (void*) CanvasJNI::drawRegion },
- {"nDrawRoundRect","(JFFFFFFJ)V", (void*) CanvasJNI::drawRoundRect},
- {"nDrawDoubleRoundRect", "(JFFFFFFFFFFFFJ)V", (void*) CanvasJNI::drawDoubleRoundRectXY},
- {"nDrawDoubleRoundRect", "(JFFFF[FFFFF[FJ)V", (void*) CanvasJNI::drawDoubleRoundRectRadii},
- {"nDrawCircle","(JFFFJ)V", (void*) CanvasJNI::drawCircle},
- {"nDrawOval","(JFFFFJ)V", (void*) CanvasJNI::drawOval},
- {"nDrawArc","(JFFFFFFZJ)V", (void*) CanvasJNI::drawArc},
- {"nDrawPath","(JJJ)V", (void*) CanvasJNI::drawPath},
- {"nDrawVertices", "(JII[FI[FI[II[SIIJ)V", (void*)CanvasJNI::drawVertices},
- {"nDrawNinePatch", "(JJJFFFFJII)V", (void*)CanvasJNI::drawNinePatch},
- {"nDrawBitmapMatrix", "(JJJJ)V", (void*)CanvasJNI::drawBitmapMatrix},
- {"nDrawBitmapMesh", "(JJII[FI[IIJ)V", (void*)CanvasJNI::drawBitmapMesh},
- {"nDrawBitmap","(JJFFJIII)V", (void*) CanvasJNI::drawBitmap},
- {"nDrawBitmap","(JJFFFFFFFFJII)V", (void*) CanvasJNI::drawBitmapRect},
- {"nDrawBitmap", "(J[IIIFFIIZJ)V", (void*)CanvasJNI::drawBitmapArray},
- {"nDrawGlyphs", "(J[I[FIIIJJ)V", (void*)CanvasJNI::drawGlyphs},
- {"nDrawText","(J[CIIFFIJ)V", (void*) CanvasJNI::drawTextChars},
- {"nDrawText","(JLjava/lang/String;IIFFIJ)V", (void*) CanvasJNI::drawTextString},
- {"nDrawTextRun","(J[CIIIIFFZJJ)V", (void*) CanvasJNI::drawTextRunChars},
- {"nDrawTextRun","(JLjava/lang/String;IIIIFFZJ)V", (void*) CanvasJNI::drawTextRunString},
- {"nDrawTextOnPath","(J[CIIJFFIJ)V", (void*) CanvasJNI::drawTextOnPathChars},
- {"nDrawTextOnPath","(JLjava/lang/String;JFFIJ)V", (void*) CanvasJNI::drawTextOnPathString},
- {"nPunchHole", "(JFFFFFFF)V", (void*) CanvasJNI::punchHole}
-};
+ {"nDrawColor", "(JII)V", (void*)CanvasJNI::drawColor},
+ {"nDrawColor", "(JJJI)V", (void*)CanvasJNI::drawColorLong},
+ {"nDrawPaint", "(JJ)V", (void*)CanvasJNI::drawPaint},
+ {"nDrawPoint", "(JFFJ)V", (void*)CanvasJNI::drawPoint},
+ {"nDrawPoints", "(J[FIIJ)V", (void*)CanvasJNI::drawPoints},
+ {"nDrawLine", "(JFFFFJ)V", (void*)CanvasJNI::drawLine},
+ {"nDrawLines", "(J[FIIJ)V", (void*)CanvasJNI::drawLines},
+ {"nDrawRect", "(JFFFFJ)V", (void*)CanvasJNI::drawRect},
+ {"nDrawRegion", "(JJJ)V", (void*)CanvasJNI::drawRegion},
+ {"nDrawRoundRect", "(JFFFFFFJ)V", (void*)CanvasJNI::drawRoundRect},
+ {"nDrawDoubleRoundRect", "(JFFFFFFFFFFFFJ)V", (void*)CanvasJNI::drawDoubleRoundRectXY},
+ {"nDrawDoubleRoundRect", "(JFFFF[FFFFF[FJ)V", (void*)CanvasJNI::drawDoubleRoundRectRadii},
+ {"nDrawCircle", "(JFFFJ)V", (void*)CanvasJNI::drawCircle},
+ {"nDrawOval", "(JFFFFJ)V", (void*)CanvasJNI::drawOval},
+ {"nDrawArc", "(JFFFFFFZJ)V", (void*)CanvasJNI::drawArc},
+ {"nDrawPath", "(JJJ)V", (void*)CanvasJNI::drawPath},
+ {"nDrawVertices", "(JII[FI[FI[II[SIIJ)V", (void*)CanvasJNI::drawVertices},
+ {"nDrawMesh", "(JJIJ)V", (void*)CanvasJNI::drawMesh},
+ {"nDrawNinePatch", "(JJJFFFFJII)V", (void*)CanvasJNI::drawNinePatch},
+ {"nDrawBitmapMatrix", "(JJJJ)V", (void*)CanvasJNI::drawBitmapMatrix},
+ {"nDrawBitmapMesh", "(JJII[FI[IIJ)V", (void*)CanvasJNI::drawBitmapMesh},
+ {"nDrawBitmap", "(JJFFJIII)V", (void*)CanvasJNI::drawBitmap},
+ {"nDrawBitmap", "(JJFFFFFFFFJII)V", (void*)CanvasJNI::drawBitmapRect},
+ {"nDrawBitmap", "(J[IIIFFIIZJ)V", (void*)CanvasJNI::drawBitmapArray},
+ {"nDrawGlyphs", "(J[I[FIIIJJ)V", (void*)CanvasJNI::drawGlyphs},
+ {"nDrawText", "(J[CIIFFIJ)V", (void*)CanvasJNI::drawTextChars},
+ {"nDrawText", "(JLjava/lang/String;IIFFIJ)V", (void*)CanvasJNI::drawTextString},
+ {"nDrawTextRun", "(J[CIIIIFFZJJ)V", (void*)CanvasJNI::drawTextRunChars},
+ {"nDrawTextRun", "(JLjava/lang/String;IIIIFFZJ)V", (void*)CanvasJNI::drawTextRunString},
+ {"nDrawTextOnPath", "(J[CIIJFFIJ)V", (void*)CanvasJNI::drawTextOnPathChars},
+ {"nDrawTextOnPath", "(JLjava/lang/String;JFFIJ)V", (void*)CanvasJNI::drawTextOnPathString},
+ {"nPunchHole", "(JFFFFFFF)V", (void*)CanvasJNI::punchHole}};
int register_android_graphics_Canvas(JNIEnv* env) {
int ret = 0;
diff --git a/libs/hwui/renderthread/CacheManager.cpp b/libs/hwui/renderthread/CacheManager.cpp
index 1d24e718db1a..1c7688464c27 100644
--- a/libs/hwui/renderthread/CacheManager.cpp
+++ b/libs/hwui/renderthread/CacheManager.cpp
@@ -98,7 +98,6 @@ void CacheManager::configureContext(GrContextOptions* contextOptions, const void
auto& cache = skiapipeline::ShaderCache::get();
cache.initShaderDiskCache(identity, size);
contextOptions->fPersistentCache = &cache;
- contextOptions->fGpuPathRenderers &= ~GpuPathRenderers::kCoverageCounting;
}
void CacheManager::trimMemory(TrimLevel mode) {
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index d09bc47cf8fd..64839d0147f8 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -71,16 +71,19 @@ CanvasContext* ScopedActiveContext::sActiveContext = nullptr;
} /* namespace */
CanvasContext* CanvasContext::create(RenderThread& thread, bool translucent,
- RenderNode* rootRenderNode, IContextFactory* contextFactory) {
+ RenderNode* rootRenderNode, IContextFactory* contextFactory,
+ int32_t uiThreadId, int32_t renderThreadId) {
auto renderType = Properties::getRenderPipelineType();
switch (renderType) {
case RenderPipelineType::SkiaGL:
return new CanvasContext(thread, translucent, rootRenderNode, contextFactory,
- std::make_unique<skiapipeline::SkiaOpenGLPipeline>(thread));
+ std::make_unique<skiapipeline::SkiaOpenGLPipeline>(thread),
+ uiThreadId, renderThreadId);
case RenderPipelineType::SkiaVulkan:
return new CanvasContext(thread, translucent, rootRenderNode, contextFactory,
- std::make_unique<skiapipeline::SkiaVulkanPipeline>(thread));
+ std::make_unique<skiapipeline::SkiaVulkanPipeline>(thread),
+ uiThreadId, renderThreadId);
default:
LOG_ALWAYS_FATAL("canvas context type %d not supported", (int32_t)renderType);
break;
@@ -110,7 +113,8 @@ void CanvasContext::prepareToDraw(const RenderThread& thread, Bitmap* bitmap) {
CanvasContext::CanvasContext(RenderThread& thread, bool translucent, RenderNode* rootRenderNode,
IContextFactory* contextFactory,
- std::unique_ptr<IRenderPipeline> renderPipeline)
+ std::unique_ptr<IRenderPipeline> renderPipeline, pid_t uiThreadId,
+ pid_t renderThreadId)
: mRenderThread(thread)
, mGenerationID(0)
, mOpaque(!translucent)
@@ -118,7 +122,8 @@ CanvasContext::CanvasContext(RenderThread& thread, bool translucent, RenderNode*
, mJankTracker(&thread.globalProfileData())
, mProfiler(mJankTracker.frames(), thread.timeLord().frameIntervalNanos())
, mContentDrawBounds(0, 0, 0, 0)
- , mRenderPipeline(std::move(renderPipeline)) {
+ , mRenderPipeline(std::move(renderPipeline))
+ , mHintSessionWrapper(uiThreadId, renderThreadId) {
mRenderThread.cacheManager().registerCanvasContext(this);
rootRenderNode->makeRoot();
mRenderNodes.emplace_back(rootRenderNode);
@@ -472,16 +477,22 @@ void CanvasContext::notifyFramePending() {
mRenderThread.pushBackFrameCallback(this);
}
-std::optional<nsecs_t> CanvasContext::draw() {
+void CanvasContext::draw() {
if (auto grContext = getGrContext()) {
if (grContext->abandoned()) {
LOG_ALWAYS_FATAL("GrContext is abandoned/device lost at start of CanvasContext::draw");
- return std::nullopt;
+ return;
}
}
SkRect dirty;
mDamageAccumulator.finish(&dirty);
+ // reset syncDelayDuration each time we draw
+ nsecs_t syncDelayDuration = mSyncDelayDuration;
+ nsecs_t idleDuration = mIdleDuration;
+ mSyncDelayDuration = 0;
+ mIdleDuration = 0;
+
if (!Properties::isDrawingEnabled() ||
(dirty.isEmpty() && Properties::skipEmptyFrames && !surfaceRequiresRedraw())) {
mCurrentFrameInfo->addFlag(FrameInfoFlags::SkippedFrame);
@@ -498,7 +509,7 @@ std::optional<nsecs_t> CanvasContext::draw() {
std::invoke(func, false /* didProduceBuffer */);
}
mFrameCommitCallbacks.clear();
- return std::nullopt;
+ return;
}
ScopedActiveContext activeContext(this);
@@ -650,10 +661,25 @@ std::optional<nsecs_t> CanvasContext::draw() {
}
}
+ int64_t intendedVsync = mCurrentFrameInfo->get(FrameInfoIndex::IntendedVsync);
+ int64_t frameDeadline = mCurrentFrameInfo->get(FrameInfoIndex::FrameDeadline);
+ int64_t dequeueBufferDuration = mCurrentFrameInfo->get(FrameInfoIndex::DequeueBufferDuration);
+
+ mHintSessionWrapper.updateTargetWorkDuration(frameDeadline - intendedVsync);
+
+ if (didDraw) {
+ int64_t frameStartTime = mCurrentFrameInfo->get(FrameInfoIndex::FrameStartTime);
+ int64_t frameDuration = systemTime(SYSTEM_TIME_MONOTONIC) - frameStartTime;
+ int64_t actualDuration = frameDuration -
+ (std::min(syncDelayDuration, mLastDequeueBufferDuration)) -
+ dequeueBufferDuration - idleDuration;
+ mHintSessionWrapper.reportActualWorkDuration(actualDuration);
+ }
+
+ mLastDequeueBufferDuration = dequeueBufferDuration;
+
mRenderThread.cacheManager().onFrameCompleted();
- return didDraw ? std::make_optional(
- mCurrentFrameInfo->get(FrameInfoIndex::DequeueBufferDuration))
- : std::nullopt;
+ return;
}
void CanvasContext::reportMetricsWithPresentTime() {
@@ -766,6 +792,8 @@ void CanvasContext::onSurfaceStatsAvailable(void* context, int32_t surfaceContro
// Called by choreographer to do an RT-driven animation
void CanvasContext::doFrame() {
if (!mRenderPipeline->isSurfaceReady()) return;
+ mIdleDuration =
+ systemTime(SYSTEM_TIME_MONOTONIC) - mRenderThread.timeLord().computeFrameTimeNanos();
prepareAndDraw(nullptr);
}
@@ -974,6 +1002,14 @@ void CanvasContext::prepareSurfaceControlForWebview() {
}
}
+void CanvasContext::sendLoadResetHint() {
+ mHintSessionWrapper.sendLoadResetHint();
+}
+
+void CanvasContext::setSyncDelayDuration(nsecs_t duration) {
+ mSyncDelayDuration = duration;
+}
+
} /* namespace renderthread */
} /* namespace uirenderer */
} /* namespace android */
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index db96cfb978e9..e875c42e9eba 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -16,10 +16,26 @@
#pragma once
+#include <SkBitmap.h>
+#include <SkRect.h>
+#include <SkSize.h>
+#include <cutils/compiler.h>
+#include <utils/Functor.h>
+#include <utils/Mutex.h>
+
+#include <functional>
+#include <future>
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "ColorMode.h"
#include "DamageAccumulator.h"
#include "FrameInfo.h"
#include "FrameInfoVisualizer.h"
#include "FrameMetricsReporter.h"
+#include "HintSessionWrapper.h"
#include "IContextFactory.h"
#include "IRenderPipeline.h"
#include "JankTracker.h"
@@ -30,21 +46,6 @@
#include "renderthread/RenderTask.h"
#include "renderthread/RenderThread.h"
#include "utils/RingBuffer.h"
-#include "ColorMode.h"
-
-#include <SkBitmap.h>
-#include <SkRect.h>
-#include <SkSize.h>
-#include <cutils/compiler.h>
-#include <utils/Functor.h>
-#include <utils/Mutex.h>
-
-#include <functional>
-#include <future>
-#include <set>
-#include <string>
-#include <utility>
-#include <vector>
namespace android {
namespace uirenderer {
@@ -66,7 +67,8 @@ class Frame;
class CanvasContext : public IFrameCallback {
public:
static CanvasContext* create(RenderThread& thread, bool translucent, RenderNode* rootRenderNode,
- IContextFactory* contextFactory);
+ IContextFactory* contextFactory, pid_t uiThreadId,
+ pid_t renderThreadId);
virtual ~CanvasContext();
/**
@@ -138,7 +140,7 @@ public:
bool makeCurrent();
void prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t syncQueued, RenderNode* target);
// Returns the DequeueBufferDuration.
- std::optional<nsecs_t> draw();
+ void draw();
void destroy();
// IFrameCallback, Choreographer-driven frame callback entry point
@@ -214,9 +216,14 @@ public:
static CanvasContext* getActiveContext();
+ void sendLoadResetHint();
+
+ void setSyncDelayDuration(nsecs_t duration);
+
private:
CanvasContext(RenderThread& thread, bool translucent, RenderNode* rootRenderNode,
- IContextFactory* contextFactory, std::unique_ptr<IRenderPipeline> renderPipeline);
+ IContextFactory* contextFactory, std::unique_ptr<IRenderPipeline> renderPipeline,
+ pid_t uiThreadId, pid_t renderThreadId);
friend class RegisterFrameCallbackTask;
// TODO: Replace with something better for layer & other GL object
@@ -330,6 +337,11 @@ private:
std::function<bool(int64_t, int64_t, int64_t)> mASurfaceTransactionCallback;
std::function<void()> mPrepareSurfaceControlForWebviewCallback;
+
+ HintSessionWrapper mHintSessionWrapper;
+ nsecs_t mLastDequeueBufferDuration = 0;
+ nsecs_t mSyncDelayDuration = 0;
+ nsecs_t mIdleDuration = 0;
};
} /* namespace renderthread */
diff --git a/libs/hwui/renderthread/DrawFrameTask.cpp b/libs/hwui/renderthread/DrawFrameTask.cpp
index cb306144ffd6..1cc82fd0ff64 100644
--- a/libs/hwui/renderthread/DrawFrameTask.cpp
+++ b/libs/hwui/renderthread/DrawFrameTask.cpp
@@ -16,7 +16,6 @@
#include "DrawFrameTask.h"
-#include <dlfcn.h>
#include <gui/TraceUtils.h>
#include <utils/Log.h>
@@ -28,70 +27,11 @@
#include "../RenderNode.h"
#include "CanvasContext.h"
#include "RenderThread.h"
-#include "thread/CommonPool.h"
-#include "utils/TimeUtils.h"
namespace android {
namespace uirenderer {
namespace renderthread {
-namespace {
-
-typedef APerformanceHintManager* (*APH_getManager)();
-typedef APerformanceHintSession* (*APH_createSession)(APerformanceHintManager*, const int32_t*,
- size_t, int64_t);
-typedef void (*APH_updateTargetWorkDuration)(APerformanceHintSession*, int64_t);
-typedef void (*APH_reportActualWorkDuration)(APerformanceHintSession*, int64_t);
-typedef void (*APH_sendHint)(APerformanceHintSession* session, int32_t);
-typedef void (*APH_closeSession)(APerformanceHintSession* session);
-
-bool gAPerformanceHintBindingInitialized = false;
-APH_getManager gAPH_getManagerFn = nullptr;
-APH_createSession gAPH_createSessionFn = nullptr;
-APH_updateTargetWorkDuration gAPH_updateTargetWorkDurationFn = nullptr;
-APH_reportActualWorkDuration gAPH_reportActualWorkDurationFn = nullptr;
-APH_sendHint gAPH_sendHintFn = nullptr;
-APH_closeSession gAPH_closeSessionFn = nullptr;
-
-void ensureAPerformanceHintBindingInitialized() {
- if (gAPerformanceHintBindingInitialized) return;
-
- void* handle_ = dlopen("libandroid.so", RTLD_NOW | RTLD_NODELETE);
- LOG_ALWAYS_FATAL_IF(handle_ == nullptr, "Failed to dlopen libandroid.so!");
-
- gAPH_getManagerFn = (APH_getManager)dlsym(handle_, "APerformanceHint_getManager");
- LOG_ALWAYS_FATAL_IF(gAPH_getManagerFn == nullptr,
- "Failed to find required symbol APerformanceHint_getManager!");
-
- gAPH_createSessionFn = (APH_createSession)dlsym(handle_, "APerformanceHint_createSession");
- LOG_ALWAYS_FATAL_IF(gAPH_createSessionFn == nullptr,
- "Failed to find required symbol APerformanceHint_createSession!");
-
- gAPH_updateTargetWorkDurationFn = (APH_updateTargetWorkDuration)dlsym(
- handle_, "APerformanceHint_updateTargetWorkDuration");
- LOG_ALWAYS_FATAL_IF(
- gAPH_updateTargetWorkDurationFn == nullptr,
- "Failed to find required symbol APerformanceHint_updateTargetWorkDuration!");
-
- gAPH_reportActualWorkDurationFn = (APH_reportActualWorkDuration)dlsym(
- handle_, "APerformanceHint_reportActualWorkDuration");
- LOG_ALWAYS_FATAL_IF(
- gAPH_reportActualWorkDurationFn == nullptr,
- "Failed to find required symbol APerformanceHint_reportActualWorkDuration!");
-
- gAPH_sendHintFn = (APH_sendHint)dlsym(handle_, "APerformanceHint_sendHint");
- LOG_ALWAYS_FATAL_IF(gAPH_sendHintFn == nullptr,
- "Failed to find required symbol APerformanceHint_sendHint!");
-
- gAPH_closeSessionFn = (APH_closeSession)dlsym(handle_, "APerformanceHint_closeSession");
- LOG_ALWAYS_FATAL_IF(gAPH_closeSessionFn == nullptr,
- "Failed to find required symbol APerformanceHint_closeSession!");
-
- gAPerformanceHintBindingInitialized = true;
-}
-
-} // namespace
-
DrawFrameTask::DrawFrameTask()
: mRenderThread(nullptr)
, mContext(nullptr)
@@ -100,13 +40,11 @@ DrawFrameTask::DrawFrameTask()
DrawFrameTask::~DrawFrameTask() {}
-void DrawFrameTask::setContext(RenderThread* thread, CanvasContext* context, RenderNode* targetNode,
- int32_t uiThreadId, int32_t renderThreadId) {
+void DrawFrameTask::setContext(RenderThread* thread, CanvasContext* context,
+ RenderNode* targetNode) {
mRenderThread = thread;
mContext = context;
mTargetNode = targetNode;
- mUiThreadId = uiThreadId;
- mRenderThreadId = renderThreadId;
}
void DrawFrameTask::pushLayerUpdate(DeferredLayerUpdater* layer) {
@@ -150,11 +88,11 @@ void DrawFrameTask::postAndWait() {
void DrawFrameTask::run() {
const int64_t vsyncId = mFrameInfo[static_cast<int>(FrameInfoIndex::FrameTimelineVsyncId)];
ATRACE_FORMAT("DrawFrames %" PRId64, vsyncId);
- nsecs_t syncDelayDuration = systemTime(SYSTEM_TIME_MONOTONIC) - mSyncQueued;
+
+ mContext->setSyncDelayDuration(systemTime(SYSTEM_TIME_MONOTONIC) - mSyncQueued);
bool canUnblockUiThread;
bool canDrawThisFrame;
- bool didDraw = false;
{
TreeInfo info(TreeInfo::MODE_FULL, *mContext);
info.forceDrawFrame = mForceDrawFrame;
@@ -175,9 +113,6 @@ void DrawFrameTask::run() {
std::function<void()> frameCompleteCallback = std::move(mFrameCompleteCallback);
mFrameCallback = nullptr;
mFrameCompleteCallback = nullptr;
- int64_t intendedVsync = mFrameInfo[static_cast<int>(FrameInfoIndex::IntendedVsync)];
- int64_t frameDeadline = mFrameInfo[static_cast<int>(FrameInfoIndex::FrameDeadline)];
- int64_t frameStartTime = mFrameInfo[static_cast<int>(FrameInfoIndex::FrameStartTime)];
// From this point on anything in "this" is *UNSAFE TO ACCESS*
if (canUnblockUiThread) {
@@ -188,18 +123,15 @@ void DrawFrameTask::run() {
if (CC_UNLIKELY(frameCallback)) {
context->enqueueFrameWork([frameCallback, context, syncResult = mSyncResult,
frameNr = context->getFrameNumber()]() {
- auto frameCommitCallback = std::move(frameCallback(syncResult, frameNr));
+ auto frameCommitCallback = frameCallback(syncResult, frameNr);
if (frameCommitCallback) {
context->addFrameCommitListener(std::move(frameCommitCallback));
}
});
}
- nsecs_t dequeueBufferDuration = 0;
if (CC_LIKELY(canDrawThisFrame)) {
- std::optional<nsecs_t> drawResult = context->draw();
- didDraw = drawResult.has_value();
- dequeueBufferDuration = drawResult.value_or(0);
+ context->draw();
} else {
// Do a flush in case syncFrameState performed any texture uploads. Since we skipped
// the draw() call, those uploads (or deletes) will end up sitting in the queue.
@@ -218,41 +150,6 @@ void DrawFrameTask::run() {
if (!canUnblockUiThread) {
unblockUiThread();
}
-
- if (!mHintSessionWrapper) mHintSessionWrapper.emplace(mUiThreadId, mRenderThreadId);
-
- constexpr int64_t kSanityCheckLowerBound = 100_us;
- constexpr int64_t kSanityCheckUpperBound = 10_s;
- int64_t targetWorkDuration = frameDeadline - intendedVsync;
- targetWorkDuration = targetWorkDuration * Properties::targetCpuTimePercentage / 100;
- if (targetWorkDuration > kSanityCheckLowerBound &&
- targetWorkDuration < kSanityCheckUpperBound &&
- targetWorkDuration != mLastTargetWorkDuration) {
- mLastTargetWorkDuration = targetWorkDuration;
- mHintSessionWrapper->updateTargetWorkDuration(targetWorkDuration);
- }
-
- if (didDraw) {
- int64_t frameDuration = systemTime(SYSTEM_TIME_MONOTONIC) - frameStartTime;
- int64_t actualDuration = frameDuration -
- (std::min(syncDelayDuration, mLastDequeueBufferDuration)) -
- dequeueBufferDuration;
- if (actualDuration > kSanityCheckLowerBound && actualDuration < kSanityCheckUpperBound) {
- mHintSessionWrapper->reportActualWorkDuration(actualDuration);
- }
- }
-
- mLastDequeueBufferDuration = dequeueBufferDuration;
-}
-
-void DrawFrameTask::sendLoadResetHint() {
- if (!(Properties::useHintManager && Properties::isDrawingEnabled())) return;
- if (!mHintSessionWrapper) mHintSessionWrapper.emplace(mUiThreadId, mRenderThreadId);
- nsecs_t now = systemTime();
- if (now - mLastFrameNotification > kResetHintTimeout) {
- mHintSessionWrapper->sendHint(SessionHint::CPU_LOAD_RESET);
- }
- mLastFrameNotification = now;
}
bool DrawFrameTask::syncFrameState(TreeInfo& info) {
@@ -305,50 +202,6 @@ void DrawFrameTask::unblockUiThread() {
mSignal.signal();
}
-DrawFrameTask::HintSessionWrapper::HintSessionWrapper(int32_t uiThreadId, int32_t renderThreadId) {
- if (!Properties::useHintManager) return;
- if (uiThreadId < 0 || renderThreadId < 0) return;
-
- ensureAPerformanceHintBindingInitialized();
-
- APerformanceHintManager* manager = gAPH_getManagerFn();
- if (!manager) return;
-
- std::vector<int32_t> tids = CommonPool::getThreadIds();
- tids.push_back(uiThreadId);
- tids.push_back(renderThreadId);
-
- // DrawFrameTask code will always set a target duration before reporting actual durations.
- // So this is just a placeholder value that's never used.
- int64_t dummyTargetDurationNanos = 16666667;
- mHintSession =
- gAPH_createSessionFn(manager, tids.data(), tids.size(), dummyTargetDurationNanos);
-}
-
-DrawFrameTask::HintSessionWrapper::~HintSessionWrapper() {
- if (mHintSession) {
- gAPH_closeSessionFn(mHintSession);
- }
-}
-
-void DrawFrameTask::HintSessionWrapper::updateTargetWorkDuration(long targetDurationNanos) {
- if (mHintSession) {
- gAPH_updateTargetWorkDurationFn(mHintSession, targetDurationNanos);
- }
-}
-
-void DrawFrameTask::HintSessionWrapper::reportActualWorkDuration(long actualDurationNanos) {
- if (mHintSession) {
- gAPH_reportActualWorkDurationFn(mHintSession, actualDurationNanos);
- }
-}
-
-void DrawFrameTask::HintSessionWrapper::sendHint(SessionHint hint) {
- if (mHintSession && Properties::isDrawingEnabled()) {
- gAPH_sendHintFn(mHintSession, static_cast<int>(hint));
- }
-}
-
} /* namespace renderthread */
} /* namespace uirenderer */
} /* namespace android */
diff --git a/libs/hwui/renderthread/DrawFrameTask.h b/libs/hwui/renderthread/DrawFrameTask.h
index 7eae41c07e64..fafab24cbce7 100644
--- a/libs/hwui/renderthread/DrawFrameTask.h
+++ b/libs/hwui/renderthread/DrawFrameTask.h
@@ -16,7 +16,6 @@
#ifndef DRAWFRAMETASK_H
#define DRAWFRAMETASK_H
-#include <android/performance_hint.h>
#include <utils/Condition.h>
#include <utils/Mutex.h>
#include <utils/StrongPointer.h>
@@ -28,7 +27,6 @@
#include "../Rect.h"
#include "../TreeInfo.h"
#include "RenderTask.h"
-#include "utils/TimeUtils.h"
namespace android {
namespace uirenderer {
@@ -62,8 +60,7 @@ public:
DrawFrameTask();
virtual ~DrawFrameTask();
- void setContext(RenderThread* thread, CanvasContext* context, RenderNode* targetNode,
- int32_t uiThreadId, int32_t renderThreadId);
+ void setContext(RenderThread* thread, CanvasContext* context, RenderNode* targetNode);
void setContentDrawBounds(int left, int top, int right, int bottom) {
mContentDrawBounds.set(left, top, right, bottom);
}
@@ -91,22 +88,7 @@ public:
void forceDrawNextFrame() { mForceDrawFrame = true; }
- void sendLoadResetHint();
-
private:
- class HintSessionWrapper {
- public:
- HintSessionWrapper(int32_t uiThreadId, int32_t renderThreadId);
- ~HintSessionWrapper();
-
- void updateTargetWorkDuration(long targetDurationNanos);
- void reportActualWorkDuration(long actualDurationNanos);
- void sendHint(SessionHint hint);
-
- private:
- APerformanceHintSession* mHintSession = nullptr;
- };
-
void postAndWait();
bool syncFrameState(TreeInfo& info);
void unblockUiThread();
@@ -117,8 +99,6 @@ private:
RenderThread* mRenderThread;
CanvasContext* mContext;
RenderNode* mTargetNode = nullptr;
- int32_t mUiThreadId = -1;
- int32_t mRenderThreadId = -1;
Rect mContentDrawBounds;
/*********************************************
@@ -135,13 +115,6 @@ private:
std::function<void(bool)> mFrameCommitCallback;
std::function<void()> mFrameCompleteCallback;
- nsecs_t mLastDequeueBufferDuration = 0;
- nsecs_t mLastTargetWorkDuration = 0;
- std::optional<HintSessionWrapper> mHintSessionWrapper;
-
- nsecs_t mLastFrameNotification = 0;
- nsecs_t kResetHintTimeout = 100_ms;
-
bool mForceDrawFrame = false;
};
diff --git a/libs/hwui/renderthread/HintSessionWrapper.cpp b/libs/hwui/renderthread/HintSessionWrapper.cpp
new file mode 100644
index 000000000000..edacef04b50a
--- /dev/null
+++ b/libs/hwui/renderthread/HintSessionWrapper.cpp
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "HintSessionWrapper.h"
+
+#include <dlfcn.h>
+#include <utils/Log.h>
+
+#include <vector>
+
+#include "../Properties.h"
+#include "thread/CommonPool.h"
+
+namespace android {
+namespace uirenderer {
+namespace renderthread {
+
+namespace {
+
+typedef APerformanceHintManager* (*APH_getManager)();
+typedef APerformanceHintSession* (*APH_createSession)(APerformanceHintManager*, const int32_t*,
+ size_t, int64_t);
+typedef void (*APH_closeSession)(APerformanceHintSession* session);
+typedef void (*APH_updateTargetWorkDuration)(APerformanceHintSession*, int64_t);
+typedef void (*APH_reportActualWorkDuration)(APerformanceHintSession*, int64_t);
+typedef void (*APH_sendHint)(APerformanceHintSession* session, int32_t);
+
+bool gAPerformanceHintBindingInitialized = false;
+APH_getManager gAPH_getManagerFn = nullptr;
+APH_createSession gAPH_createSessionFn = nullptr;
+APH_closeSession gAPH_closeSessionFn = nullptr;
+APH_updateTargetWorkDuration gAPH_updateTargetWorkDurationFn = nullptr;
+APH_reportActualWorkDuration gAPH_reportActualWorkDurationFn = nullptr;
+APH_sendHint gAPH_sendHintFn = nullptr;
+
+void ensureAPerformanceHintBindingInitialized() {
+ if (gAPerformanceHintBindingInitialized) return;
+
+ void* handle_ = dlopen("libandroid.so", RTLD_NOW | RTLD_NODELETE);
+ LOG_ALWAYS_FATAL_IF(handle_ == nullptr, "Failed to dlopen libandroid.so!");
+
+ gAPH_getManagerFn = (APH_getManager)dlsym(handle_, "APerformanceHint_getManager");
+ LOG_ALWAYS_FATAL_IF(gAPH_getManagerFn == nullptr,
+ "Failed to find required symbol APerformanceHint_getManager!");
+
+ gAPH_createSessionFn = (APH_createSession)dlsym(handle_, "APerformanceHint_createSession");
+ LOG_ALWAYS_FATAL_IF(gAPH_createSessionFn == nullptr,
+ "Failed to find required symbol APerformanceHint_createSession!");
+
+ gAPH_closeSessionFn = (APH_closeSession)dlsym(handle_, "APerformanceHint_closeSession");
+ LOG_ALWAYS_FATAL_IF(gAPH_closeSessionFn == nullptr,
+ "Failed to find required symbol APerformanceHint_closeSession!");
+
+ gAPH_updateTargetWorkDurationFn = (APH_updateTargetWorkDuration)dlsym(
+ handle_, "APerformanceHint_updateTargetWorkDuration");
+ LOG_ALWAYS_FATAL_IF(
+ gAPH_updateTargetWorkDurationFn == nullptr,
+ "Failed to find required symbol APerformanceHint_updateTargetWorkDuration!");
+
+ gAPH_reportActualWorkDurationFn = (APH_reportActualWorkDuration)dlsym(
+ handle_, "APerformanceHint_reportActualWorkDuration");
+ LOG_ALWAYS_FATAL_IF(
+ gAPH_reportActualWorkDurationFn == nullptr,
+ "Failed to find required symbol APerformanceHint_reportActualWorkDuration!");
+
+ gAPH_sendHintFn = (APH_sendHint)dlsym(handle_, "APerformanceHint_sendHint");
+ LOG_ALWAYS_FATAL_IF(gAPH_sendHintFn == nullptr,
+ "Failed to find required symbol APerformanceHint_sendHint!");
+
+ gAPerformanceHintBindingInitialized = true;
+}
+
+} // namespace
+
+HintSessionWrapper::HintSessionWrapper(pid_t uiThreadId, pid_t renderThreadId)
+ : mUiThreadId(uiThreadId), mRenderThreadId(renderThreadId) {}
+
+HintSessionWrapper::~HintSessionWrapper() {
+ if (mHintSession) {
+ gAPH_closeSessionFn(mHintSession);
+ }
+}
+
+bool HintSessionWrapper::useHintSession() {
+ if (!Properties::useHintManager || !Properties::isDrawingEnabled()) return false;
+ if (mHintSession) return true;
+ // If session does not exist, create it;
+ // this defers session creation until we try to actually use it.
+ if (!mSessionValid) return false;
+ return init();
+}
+
+bool HintSessionWrapper::init() {
+ if (mUiThreadId < 0 || mRenderThreadId < 0) return false;
+
+ // Assume that if we return before the end, it broke
+ mSessionValid = false;
+
+ ensureAPerformanceHintBindingInitialized();
+
+ APerformanceHintManager* manager = gAPH_getManagerFn();
+ if (!manager) return false;
+
+ std::vector<pid_t> tids = CommonPool::getThreadIds();
+ tids.push_back(mUiThreadId);
+ tids.push_back(mRenderThreadId);
+
+ // Use a placeholder target value to initialize,
+ // this will always be replaced elsewhere before it gets used
+ int64_t defaultTargetDurationNanos = 16666667;
+ mHintSession =
+ gAPH_createSessionFn(manager, tids.data(), tids.size(), defaultTargetDurationNanos);
+
+ mSessionValid = !!mHintSession;
+ return mSessionValid;
+}
+
+void HintSessionWrapper::updateTargetWorkDuration(long targetWorkDurationNanos) {
+ if (!useHintSession()) return;
+ targetWorkDurationNanos = targetWorkDurationNanos * Properties::targetCpuTimePercentage / 100;
+ if (targetWorkDurationNanos != mLastTargetWorkDuration &&
+ targetWorkDurationNanos > kSanityCheckLowerBound &&
+ targetWorkDurationNanos < kSanityCheckUpperBound) {
+ mLastTargetWorkDuration = targetWorkDurationNanos;
+ gAPH_updateTargetWorkDurationFn(mHintSession, targetWorkDurationNanos);
+ }
+ mLastFrameNotification = systemTime();
+}
+
+void HintSessionWrapper::reportActualWorkDuration(long actualDurationNanos) {
+ if (!useHintSession()) return;
+ if (actualDurationNanos > kSanityCheckLowerBound &&
+ actualDurationNanos < kSanityCheckUpperBound) {
+ gAPH_reportActualWorkDurationFn(mHintSession, actualDurationNanos);
+ }
+}
+
+void HintSessionWrapper::sendLoadResetHint() {
+ if (!useHintSession()) return;
+ nsecs_t now = systemTime();
+ if (now - mLastFrameNotification > kResetHintTimeout) {
+ gAPH_sendHintFn(mHintSession, static_cast<int>(SessionHint::CPU_LOAD_RESET));
+ }
+ mLastFrameNotification = now;
+}
+
+} /* namespace renderthread */
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/renderthread/HintSessionWrapper.h b/libs/hwui/renderthread/HintSessionWrapper.h
new file mode 100644
index 000000000000..fcbc10185255
--- /dev/null
+++ b/libs/hwui/renderthread/HintSessionWrapper.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <android/performance_hint.h>
+
+#include "utils/TimeUtils.h"
+
+namespace android {
+namespace uirenderer {
+
+namespace renderthread {
+
+class HintSessionWrapper {
+public:
+ HintSessionWrapper(pid_t uiThreadId, pid_t renderThreadId);
+ ~HintSessionWrapper();
+
+ void updateTargetWorkDuration(long targetDurationNanos);
+ void reportActualWorkDuration(long actualDurationNanos);
+ void sendLoadResetHint();
+
+private:
+ bool useHintSession();
+ bool init();
+ APerformanceHintSession* mHintSession = nullptr;
+
+ nsecs_t mLastFrameNotification = 0;
+ nsecs_t mLastTargetWorkDuration = 0;
+
+ pid_t mUiThreadId;
+ pid_t mRenderThreadId;
+
+ bool mSessionValid = true;
+
+ static constexpr nsecs_t kResetHintTimeout = 100_ms;
+ static constexpr int64_t kSanityCheckLowerBound = 100_us;
+ static constexpr int64_t kSanityCheckUpperBound = 10_s;
+};
+
+} /* namespace renderthread */
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index 03a2bc9f7e51..07f5a78fc1ec 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -42,11 +42,13 @@ namespace renderthread {
RenderProxy::RenderProxy(bool translucent, RenderNode* rootRenderNode,
IContextFactory* contextFactory)
: mRenderThread(RenderThread::getInstance()), mContext(nullptr) {
- mContext = mRenderThread.queue().runSync([&]() -> CanvasContext* {
- return CanvasContext::create(mRenderThread, translucent, rootRenderNode, contextFactory);
+ pid_t uiThreadId = pthread_gettid_np(pthread_self());
+ pid_t renderThreadId = getRenderThreadTid();
+ mContext = mRenderThread.queue().runSync([=, this]() -> CanvasContext* {
+ return CanvasContext::create(mRenderThread, translucent, rootRenderNode, contextFactory,
+ uiThreadId, renderThreadId);
});
- mDrawFrameTask.setContext(&mRenderThread, mContext, rootRenderNode,
- pthread_gettid_np(pthread_self()), getRenderThreadTid());
+ mDrawFrameTask.setContext(&mRenderThread, mContext, rootRenderNode);
}
RenderProxy::~RenderProxy() {
@@ -55,7 +57,7 @@ RenderProxy::~RenderProxy() {
void RenderProxy::destroyContext() {
if (mContext) {
- mDrawFrameTask.setContext(nullptr, nullptr, nullptr, -1, -1);
+ mDrawFrameTask.setContext(nullptr, nullptr, nullptr);
// This is also a fence as we need to be certain that there are no
// outstanding mDrawFrame tasks posted before it is destroyed
mRenderThread.queue().runSync([this]() { delete mContext; });
@@ -237,7 +239,7 @@ void RenderProxy::notifyFramePending() {
}
void RenderProxy::notifyCallbackPending() {
- mDrawFrameTask.sendLoadResetHint();
+ mRenderThread.queue().post([this]() { mContext->sendLoadResetHint(); });
}
void RenderProxy::dumpProfileInfo(int fd, int dumpFlags) {
diff --git a/libs/hwui/tests/common/TestContext.cpp b/libs/hwui/tests/common/TestContext.cpp
index 0faa8f4f6cc9..fd596d998dfd 100644
--- a/libs/hwui/tests/common/TestContext.cpp
+++ b/libs/hwui/tests/common/TestContext.cpp
@@ -31,10 +31,8 @@ const ui::StaticDisplayInfo& getDisplayInfo() {
const std::vector<PhysicalDisplayId> ids = SurfaceComposerClient::getPhysicalDisplayIds();
LOG_ALWAYS_FATAL_IF(ids.empty(), "%s: No displays", __FUNCTION__);
- const sp<IBinder> token = SurfaceComposerClient::getPhysicalDisplayToken(ids.front());
- LOG_ALWAYS_FATAL_IF(!token, "%s: No internal display", __FUNCTION__);
-
- const status_t status = SurfaceComposerClient::getStaticDisplayInfo(token, &info);
+ const status_t status =
+ SurfaceComposerClient::getStaticDisplayInfo(ids.front().value, &info);
LOG_ALWAYS_FATAL_IF(status, "%s: Failed to get display info", __FUNCTION__);
#endif
return info;
diff --git a/libs/hwui/tests/unit/CanvasContextTests.cpp b/libs/hwui/tests/unit/CanvasContextTests.cpp
index 1771c3590e10..88420a5d5c23 100644
--- a/libs/hwui/tests/unit/CanvasContextTests.cpp
+++ b/libs/hwui/tests/unit/CanvasContextTests.cpp
@@ -36,7 +36,7 @@ RENDERTHREAD_TEST(CanvasContext, create) {
auto rootNode = TestUtils::createNode(0, 0, 200, 400, nullptr);
ContextFactory contextFactory;
std::unique_ptr<CanvasContext> canvasContext(
- CanvasContext::create(renderThread, false, rootNode.get(), &contextFactory));
+ CanvasContext::create(renderThread, false, rootNode.get(), &contextFactory, 0, 0));
ASSERT_FALSE(canvasContext->hasSurface());
diff --git a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
index 3caba2d410bd..596bd37e4cf5 100644
--- a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
+++ b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
@@ -335,7 +335,7 @@ RENDERTHREAD_TEST(RenderNodeDrawable, projectionReorder) {
"A");
ContextFactory contextFactory;
std::unique_ptr<CanvasContext> canvasContext(
- CanvasContext::create(renderThread, false, parent.get(), &contextFactory));
+ CanvasContext::create(renderThread, false, parent.get(), &contextFactory, 0, 0));
TreeInfo info(TreeInfo::MODE_RT_ONLY, *canvasContext.get());
DamageAccumulator damageAccumulator;
info.damageAccumulator = &damageAccumulator;
@@ -399,7 +399,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(RenderNodeDrawable, emptyReceiver) {
"A");
ContextFactory contextFactory;
std::unique_ptr<CanvasContext> canvasContext(
- CanvasContext::create(renderThread, false, parent.get(), &contextFactory));
+ CanvasContext::create(renderThread, false, parent.get(), &contextFactory, 0, 0));
TreeInfo info(TreeInfo::MODE_RT_ONLY, *canvasContext.get());
DamageAccumulator damageAccumulator;
info.damageAccumulator = &damageAccumulator;
@@ -519,7 +519,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(RenderNodeDrawable, projectionHwLayer) {
// prepareTree is required to find, which receivers have backward projected nodes
ContextFactory contextFactory;
std::unique_ptr<CanvasContext> canvasContext(
- CanvasContext::create(renderThread, false, parent.get(), &contextFactory));
+ CanvasContext::create(renderThread, false, parent.get(), &contextFactory, 0, 0));
TreeInfo info(TreeInfo::MODE_RT_ONLY, *canvasContext.get());
DamageAccumulator damageAccumulator;
info.damageAccumulator = &damageAccumulator;
@@ -619,7 +619,7 @@ RENDERTHREAD_TEST(RenderNodeDrawable, projectionChildScroll) {
// prepareTree is required to find, which receivers have backward projected nodes
ContextFactory contextFactory;
std::unique_ptr<CanvasContext> canvasContext(
- CanvasContext::create(renderThread, false, parent.get(), &contextFactory));
+ CanvasContext::create(renderThread, false, parent.get(), &contextFactory, 0, 0));
TreeInfo info(TreeInfo::MODE_RT_ONLY, *canvasContext.get());
DamageAccumulator damageAccumulator;
info.damageAccumulator = &damageAccumulator;
@@ -635,7 +635,7 @@ namespace {
static int drawNode(RenderThread& renderThread, const sp<RenderNode>& renderNode) {
ContextFactory contextFactory;
std::unique_ptr<CanvasContext> canvasContext(
- CanvasContext::create(renderThread, false, renderNode.get(), &contextFactory));
+ CanvasContext::create(renderThread, false, renderNode.get(), &contextFactory, 0, 0));
TreeInfo info(TreeInfo::MODE_RT_ONLY, *canvasContext.get());
DamageAccumulator damageAccumulator;
info.damageAccumulator = &damageAccumulator;
diff --git a/libs/hwui/tests/unit/RenderNodeTests.cpp b/libs/hwui/tests/unit/RenderNodeTests.cpp
index 61bd646b0a76..80796f4a4111 100644
--- a/libs/hwui/tests/unit/RenderNodeTests.cpp
+++ b/libs/hwui/tests/unit/RenderNodeTests.cpp
@@ -274,7 +274,7 @@ RENDERTHREAD_TEST(RenderNode, prepareTree_nullableDisplayList) {
auto rootNode = TestUtils::createNode(0, 0, 200, 400, nullptr);
ContextFactory contextFactory;
std::unique_ptr<CanvasContext> canvasContext(
- CanvasContext::create(renderThread, false, rootNode.get(), &contextFactory));
+ CanvasContext::create(renderThread, false, rootNode.get(), &contextFactory, 0, 0));
TreeInfo info(TreeInfo::MODE_RT_ONLY, *canvasContext.get());
DamageAccumulator damageAccumulator;
info.damageAccumulator = &damageAccumulator;
@@ -310,7 +310,7 @@ RENDERTHREAD_TEST(DISABLED_RenderNode, prepareTree_HwLayer_AVD_enqueueDamage) {
});
ContextFactory contextFactory;
std::unique_ptr<CanvasContext> canvasContext(
- CanvasContext::create(renderThread, false, rootNode.get(), &contextFactory));
+ CanvasContext::create(renderThread, false, rootNode.get(), &contextFactory, 0, 0));
canvasContext->setSurface(nullptr);
TreeInfo info(TreeInfo::MODE_RT_ONLY, *canvasContext.get());
DamageAccumulator damageAccumulator;
diff --git a/libs/hwui/tests/unit/SkiaDisplayListTests.cpp b/libs/hwui/tests/unit/SkiaDisplayListTests.cpp
index 3d5aca4bf05a..f825d7c5d9cc 100644
--- a/libs/hwui/tests/unit/SkiaDisplayListTests.cpp
+++ b/libs/hwui/tests/unit/SkiaDisplayListTests.cpp
@@ -142,7 +142,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaDisplayList, prepareListAndChildren) {
auto rootNode = TestUtils::createNode(0, 0, 200, 400, nullptr);
ContextFactory contextFactory;
std::unique_ptr<CanvasContext> canvasContext(
- CanvasContext::create(renderThread, false, rootNode.get(), &contextFactory));
+ CanvasContext::create(renderThread, false, rootNode.get(), &contextFactory, 0, 0));
TreeInfo info(TreeInfo::MODE_FULL, *canvasContext.get());
DamageAccumulator damageAccumulator;
info.damageAccumulator = &damageAccumulator;
@@ -201,7 +201,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaDisplayList, prepareListAndChildren_vdOffscr
auto rootNode = TestUtils::createNode(0, 0, 200, 400, nullptr);
ContextFactory contextFactory;
std::unique_ptr<CanvasContext> canvasContext(
- CanvasContext::create(renderThread, false, rootNode.get(), &contextFactory));
+ CanvasContext::create(renderThread, false, rootNode.get(), &contextFactory, 0, 0));
// Set up a Surface so that we can position the VectorDrawable offscreen.
test::TestContext testContext;
diff --git a/location/java/android/location/altitude/AltitudeConverter.java b/location/java/android/location/altitude/AltitudeConverter.java
new file mode 100644
index 000000000000..d46b4d2c3be3
--- /dev/null
+++ b/location/java/android/location/altitude/AltitudeConverter.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.location.altitude;
+
+import android.annotation.NonNull;
+import android.annotation.WorkerThread;
+import android.content.Context;
+import android.location.Location;
+
+import com.android.internal.location.altitude.GeoidHeightMap;
+import com.android.internal.location.altitude.S2CellIdUtils;
+import com.android.internal.location.altitude.nano.MapParamsProto;
+import com.android.internal.util.Preconditions;
+
+import java.io.IOException;
+
+/**
+ * Converts altitudes reported above the World Geodetic System 1984 (WGS84) reference ellipsoid
+ * into ones above Mean Sea Level.
+ */
+public final class AltitudeConverter {
+
+ private static final double MAX_ABS_VALID_LATITUDE = 90;
+ private static final double MAX_ABS_VALID_LONGITUDE = 180;
+
+ /** Manages a mapping of geoid heights associated with S2 cells. */
+ private final GeoidHeightMap mGeoidHeightMap = new GeoidHeightMap();
+
+ /**
+ * Creates an instance that manages an independent cache to optimized conversions of locations
+ * in proximity to one another.
+ */
+ public AltitudeConverter() {
+ }
+
+ /**
+ * Throws an {@link IllegalArgumentException} if the {@code location} has an invalid latitude,
+ * longitude, or altitude above WGS84.
+ */
+ private static void validate(@NonNull Location location) {
+ Preconditions.checkArgument(
+ isFiniteAndAtAbsMost(location.getLatitude(), MAX_ABS_VALID_LATITUDE),
+ "Invalid latitude: %f", location.getLatitude());
+ Preconditions.checkArgument(
+ isFiniteAndAtAbsMost(location.getLongitude(), MAX_ABS_VALID_LONGITUDE),
+ "Invalid longitude: %f", location.getLongitude());
+ Preconditions.checkArgument(location.hasAltitude(), "Missing altitude above WGS84");
+ Preconditions.checkArgument(Double.isFinite(location.getAltitude()),
+ "Invalid altitude above WGS84: %f", location.getAltitude());
+ }
+
+ private static boolean isFiniteAndAtAbsMost(double value, double rhs) {
+ return Double.isFinite(value) && Math.abs(value) <= rhs;
+ }
+
+ /**
+ * Returns the four S2 cell IDs for the map square associated with the {@code location}.
+ *
+ * <p>The first map cell contains the location, while the others are located horizontally,
+ * vertically, and diagonally, in that order, with respect to the S2 (i,j) coordinate system. If
+ * the diagonal map cell does not exist (i.e., the location is near an S2 cube vertex), its
+ * corresponding ID is set to zero.
+ */
+ @NonNull
+ private static long[] findMapSquare(@NonNull MapParamsProto params,
+ @NonNull Location location) {
+ long s2CellId = S2CellIdUtils.fromLatLngDegrees(location.getLatitude(),
+ location.getLongitude());
+
+ // (0,0) cell.
+ long s0 = S2CellIdUtils.getParent(s2CellId, params.mapS2Level);
+ long[] edgeNeighbors = new long[4];
+ S2CellIdUtils.getEdgeNeighbors(s0, edgeNeighbors);
+
+ // (1,0) cell.
+ int i1 = S2CellIdUtils.getI(s2CellId) > S2CellIdUtils.getI(s0) ? -1 : 1;
+ long s1 = edgeNeighbors[i1 + 2];
+
+ // (0,1) cell.
+ int i2 = S2CellIdUtils.getJ(s2CellId) > S2CellIdUtils.getJ(s0) ? 1 : -1;
+ long s2 = edgeNeighbors[i2 + 1];
+
+ // (1,1) cell.
+ S2CellIdUtils.getEdgeNeighbors(s1, edgeNeighbors);
+ long s3 = 0;
+ for (int i = 0; i < edgeNeighbors.length; i++) {
+ if (edgeNeighbors[i] == s0) {
+ int i3 = (i + i1 * i2 + edgeNeighbors.length) % edgeNeighbors.length;
+ s3 = edgeNeighbors[i3] == s2 ? 0 : edgeNeighbors[i3];
+ break;
+ }
+ }
+
+ // Reuse edge neighbors' array to avoid an extra allocation.
+ edgeNeighbors[0] = s0;
+ edgeNeighbors[1] = s1;
+ edgeNeighbors[2] = s2;
+ edgeNeighbors[3] = s3;
+ return edgeNeighbors;
+ }
+
+ /**
+ * Adds to {@code location} the bilinearly interpolated Mean Sea Level altitude. In addition, a
+ * Mean Sea Level altitude accuracy is added if the {@code location} has a valid vertical
+ * accuracy; otherwise, does not add a corresponding accuracy.
+ */
+ private static void addMslAltitude(@NonNull MapParamsProto params, @NonNull long[] s2CellIds,
+ @NonNull double[] geoidHeightsMeters, @NonNull Location location) {
+ long s0 = s2CellIds[0];
+ double h0 = geoidHeightsMeters[0];
+ double h1 = geoidHeightsMeters[1];
+ double h2 = geoidHeightsMeters[2];
+ double h3 = s2CellIds[3] == 0 ? h0 : geoidHeightsMeters[3];
+
+ // Bilinear interpolation on an S2 square of size equal to that of a map cell. wi and wj
+ // are the normalized [0,1] weights in the i and j directions, respectively, allowing us to
+ // employ the simplified unit square formulation.
+ long s2CellId = S2CellIdUtils.fromLatLngDegrees(location.getLatitude(),
+ location.getLongitude());
+ double sizeIj = 1 << (S2CellIdUtils.MAX_LEVEL - params.mapS2Level);
+ double wi = Math.abs(S2CellIdUtils.getI(s2CellId) - S2CellIdUtils.getI(s0)) / sizeIj;
+ double wj = Math.abs(S2CellIdUtils.getJ(s2CellId) - S2CellIdUtils.getJ(s0)) / sizeIj;
+ double offsetMeters = h0 + (h1 - h0) * wi + (h2 - h0) * wj + (h3 - h1 - h2 + h0) * wi * wj;
+
+ location.setMslAltitudeMeters(location.getAltitude() - offsetMeters);
+ if (location.hasVerticalAccuracy()) {
+ double verticalAccuracyMeters = location.getVerticalAccuracyMeters();
+ if (Double.isFinite(verticalAccuracyMeters) && verticalAccuracyMeters >= 0) {
+ location.setMslAltitudeAccuracyMeters(
+ (float) Math.hypot(verticalAccuracyMeters, params.modelRmseMeters));
+ }
+ }
+ }
+
+ /**
+ * Adds a Mean Sea Level altitude to the {@code location}. In addition, adds a Mean Sea Level
+ * altitude accuracy if the {@code location} has a finite and non-negative vertical accuracy;
+ * otherwise, does not add a corresponding accuracy.
+ *
+ * <p>Must be called off the main thread as data may be loaded from raw assets.
+ *
+ * @throws IOException if an I/O error occurs when loading data from raw assets.
+ * @throws IllegalArgumentException if the {@code location} has an invalid latitude, longitude,
+ * or altitude above WGS84. Specifically, the latitude must be
+ * between -90 and 90 (both inclusive), the longitude must be
+ * between -180 and 180 (both inclusive), and the altitude
+ * above WGS84 must be finite.
+ */
+ @WorkerThread
+ public void addMslAltitudeToLocation(@NonNull Context context, @NonNull Location location)
+ throws IOException {
+ validate(location);
+ MapParamsProto params = GeoidHeightMap.getParams(context);
+ long[] s2CellIds = findMapSquare(params, location);
+ double[] geoidHeightsMeters = mGeoidHeightMap.readGeoidHeights(params, context, s2CellIds);
+ addMslAltitude(params, s2CellIds, geoidHeightsMeters, location);
+ }
+}
diff --git a/location/java/com/android/internal/location/altitude/GeoidHeightMap.java b/location/java/com/android/internal/location/altitude/GeoidHeightMap.java
new file mode 100644
index 000000000000..6430eb4efad2
--- /dev/null
+++ b/location/java/com/android/internal/location/altitude/GeoidHeightMap.java
@@ -0,0 +1,367 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.location.altitude;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.util.LruCache;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.location.altitude.nano.MapParamsProto;
+import com.android.internal.location.altitude.nano.S2TileProto;
+import com.android.internal.util.Preconditions;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.util.Objects;
+
+/**
+ * Manages a mapping of geoid heights associated with S2 cells, referred to as MAP CELLS.
+ *
+ * <p>Tiles are used extensively to reduce the number of entries needed to be stored in memory and
+ * on disk. A tile associates geoid heights with all map cells of a common parent at a specified S2
+ * level.
+ *
+ * <p>Since bilinear interpolation considers at most four map cells at a time, at most four tiles
+ * are simultaneously stored in memory. These tiles, referred to as CACHE TILES, are each keyed by
+ * its common parent's S2 cell ID, referred to as a CACHE KEY.
+ *
+ * <p>Absent cache tiles needed for interpolation are constructed from larger tiles stored on disk.
+ * The latter tiles, referred to as DISK TILES, are each keyed by its common parent's S2 cell token,
+ * referred to as a DISK TOKEN.
+ */
+public final class GeoidHeightMap {
+
+ private static final Object sLock = new Object();
+
+ @GuardedBy("sLock")
+ @Nullable
+ private static MapParamsProto sParams;
+
+ /** Defines a cache large enough to hold all cache tiles needed for interpolation. */
+ private final LruCache<Long, S2TileProto> mCacheTiles = new LruCache<>(4);
+
+ /**
+ * Returns the singleton parameter instance for a spherically projected geoid height map and its
+ * corresponding tile management.
+ */
+ @NonNull
+ public static MapParamsProto getParams(@NonNull Context context) throws IOException {
+ synchronized (sLock) {
+ if (sParams == null) {
+ try (InputStream is = context.getApplicationContext().getAssets().open(
+ "geoid_height_map/map-params.pb")) {
+ sParams = MapParamsProto.parseFrom(is.readAllBytes());
+ }
+ }
+ return sParams;
+ }
+ }
+
+ private static long getCacheKey(@NonNull MapParamsProto params, long s2CellId) {
+ return S2CellIdUtils.getParent(s2CellId, params.cacheTileS2Level);
+ }
+
+ @NonNull
+ private static String getDiskToken(@NonNull MapParamsProto params, long s2CellId) {
+ return S2CellIdUtils.getToken(
+ S2CellIdUtils.getParent(s2CellId, params.diskTileS2Level));
+ }
+
+ /**
+ * Adds to {@code values} values in the unit interval [0, 1] for the map cells identified by
+ * {@code s2CellIds}. Returns true if values are present for all non-zero IDs; otherwise,
+ * returns false and adds NaNs for absent values.
+ */
+ private static boolean getUnitIntervalValues(@NonNull MapParamsProto params,
+ @NonNull TileFunction tileFunction,
+ @NonNull long[] s2CellIds, @NonNull double[] values) {
+ int len = s2CellIds.length;
+
+ S2TileProto[] tiles = new S2TileProto[len];
+ for (int i = 0; i < len; i++) {
+ if (s2CellIds[i] != 0) {
+ tiles[i] = tileFunction.getTile(s2CellIds[i]);
+ }
+ values[i] = Double.NaN;
+ }
+
+ for (int i = 0; i < len; i++) {
+ if (tiles[i] == null || !Double.isNaN(values[i])) {
+ continue;
+ }
+
+ mergeByteBufferValues(params, s2CellIds, tiles, i, values);
+ mergeByteJpegValues(params, s2CellIds, tiles, i, values);
+ mergeBytePngValues(params, s2CellIds, tiles, i, values);
+ }
+
+ boolean allFound = true;
+ for (int i = 0; i < len; i++) {
+ if (s2CellIds[i] == 0) {
+ continue;
+ }
+ if (Double.isNaN(values[i])) {
+ allFound = false;
+ } else {
+ values[i] = (((int) values[i]) & 0xFF) / 255.0;
+ }
+ }
+ return allFound;
+ }
+
+ @SuppressWarnings("ReferenceEquality")
+ private static void mergeByteBufferValues(@NonNull MapParamsProto params,
+ @NonNull long[] s2CellIds,
+ @NonNull S2TileProto[] tiles,
+ int tileIndex, @NonNull double[] values) {
+ byte[] bytes = tiles[tileIndex].byteBuffer;
+ if (bytes == null || bytes.length == 0) {
+ return;
+ }
+
+ ByteBuffer byteBuffer = ByteBuffer.wrap(bytes).asReadOnlyBuffer();
+ int tileS2Level = params.mapS2Level - Integer.numberOfTrailingZeros(byteBuffer.limit()) / 2;
+ int numBitsLeftOfTile = 2 * tileS2Level + 3;
+
+ for (int i = tileIndex; i < tiles.length; i++) {
+ if (tiles[i] != tiles[tileIndex]) {
+ continue;
+ }
+
+ long maskedS2CellId = s2CellIds[i] & (-1L >>> numBitsLeftOfTile);
+ int numBitsRightOfMap = 2 * (S2CellIdUtils.MAX_LEVEL - params.mapS2Level) + 1;
+ int bufferIndex = (int) (maskedS2CellId >>> numBitsRightOfMap);
+ values[i] = Double.isNaN(values[i]) ? 0 : values[i];
+ values[i] += ((int) byteBuffer.get(bufferIndex)) & 0xFF;
+ }
+ }
+
+ private static void mergeByteJpegValues(@NonNull MapParamsProto params,
+ @NonNull long[] s2CellIds,
+ @NonNull S2TileProto[] tiles,
+ int tileIndex, @NonNull double[] values) {
+ mergeByteImageValues(params, tiles[tileIndex].byteJpeg, s2CellIds, tiles, tileIndex,
+ values);
+ }
+
+ private static void mergeBytePngValues(@NonNull MapParamsProto params,
+ @NonNull long[] s2CellIds,
+ @NonNull S2TileProto[] tiles,
+ int tileIndex, @NonNull double[] values) {
+ mergeByteImageValues(params, tiles[tileIndex].bytePng, s2CellIds, tiles, tileIndex, values);
+ }
+
+ @SuppressWarnings("ReferenceEquality")
+ private static void mergeByteImageValues(@NonNull MapParamsProto params, @NonNull byte[] bytes,
+ @NonNull long[] s2CellIds,
+ @NonNull S2TileProto[] tiles, int tileIndex, @NonNull double[] values) {
+ if (bytes == null || bytes.length == 0) {
+ return;
+ }
+ Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
+ if (bitmap == null) {
+ return;
+ }
+
+ for (int i = tileIndex; i < tiles.length; i++) {
+ if (s2CellIds[i] == 0 || tiles[i] != tiles[tileIndex]) {
+ continue;
+ }
+
+ values[i] = Double.isNaN(values[i]) ? 0 : values[i];
+ values[i] += bitmap.getPixel(getIndexX(params, s2CellIds[i], bitmap.getWidth()),
+ getIndexY(params, s2CellIds[i], bitmap.getHeight())) & 0xFF;
+ }
+ }
+
+ /** Returns the X index for an S2 cell within an S2 tile image of specified width. */
+ private static int getIndexX(@NonNull MapParamsProto params, long s2CellId, int width) {
+ return getIndexXOrY(params, S2CellIdUtils.getI(s2CellId), width);
+ }
+
+ /** Returns the Y index for an S2 cell within an S2 tile image of specified height. */
+ private static int getIndexY(@NonNull MapParamsProto params, long s2CellId, int height) {
+ return getIndexXOrY(params, S2CellIdUtils.getJ(s2CellId), height);
+ }
+
+ private static int getIndexXOrY(@NonNull MapParamsProto params, int iOrJ, int widthOrHeight) {
+ return (iOrJ >> (S2CellIdUtils.MAX_LEVEL - params.mapS2Level)) % widthOrHeight;
+ }
+
+ /**
+ * Returns the geoid heights in meters associated with the map cells identified by
+ * {@code s2CellIds}. Throws an {@link IOException} if a geoid height cannot be calculated for a
+ * non-zero ID.
+ */
+ @NonNull
+ public double[] readGeoidHeights(@NonNull MapParamsProto params, @NonNull Context context,
+ @NonNull long[] s2CellIds) throws IOException {
+ Preconditions.checkArgument(s2CellIds.length == 4);
+ for (long s2CellId : s2CellIds) {
+ Preconditions.checkArgument(
+ s2CellId == 0 || S2CellIdUtils.getLevel(s2CellId) == params.mapS2Level);
+ }
+
+ double[] heightsMeters = new double[s2CellIds.length];
+ if (getGeoidHeights(params, mCacheTiles::get, s2CellIds, heightsMeters)) {
+ return heightsMeters;
+ }
+
+ TileFunction loadedTiles = loadFromCacheAndDisk(params, context, s2CellIds);
+ if (getGeoidHeights(params, loadedTiles, s2CellIds, heightsMeters)) {
+ return heightsMeters;
+ }
+ throw new IOException("Unable to calculate geoid heights from raw assets.");
+ }
+
+ /**
+ * Adds to {@code heightsMeters} the geoid heights in meters associated with the map cells
+ * identified by {@code s2CellIds}. Returns true if heights are present for all non-zero IDs;
+ * otherwise, returns false and adds NaNs for absent heights.
+ */
+ private boolean getGeoidHeights(@NonNull MapParamsProto params,
+ @NonNull TileFunction tileFunction, @NonNull long[] s2CellIds,
+ @NonNull double[] heightsMeters) {
+ boolean allFound = getUnitIntervalValues(params, tileFunction, s2CellIds, heightsMeters);
+ for (int i = 0; i < heightsMeters.length; i++) {
+ // NaNs are properly preserved.
+ heightsMeters[i] *= params.modelAMeters;
+ heightsMeters[i] += params.modelBMeters;
+ }
+ return allFound;
+ }
+
+ @NonNull
+ private TileFunction loadFromCacheAndDisk(@NonNull MapParamsProto params,
+ @NonNull Context context, @NonNull long[] s2CellIds) throws IOException {
+ int len = s2CellIds.length;
+
+ // Enable batch loading by finding all cache keys upfront.
+ long[] cacheKeys = new long[len];
+ for (int i = 0; i < len; i++) {
+ if (s2CellIds[i] == 0) {
+ continue;
+ }
+ cacheKeys[i] = getCacheKey(params, s2CellIds[i]);
+ }
+
+ // Attempt to load tiles from cache.
+ S2TileProto[] loadedTiles = new S2TileProto[len];
+ String[] diskTokens = new String[len];
+ for (int i = 0; i < len; i++) {
+ if (s2CellIds[i] == 0 || diskTokens[i] != null) {
+ continue;
+ }
+ loadedTiles[i] = mCacheTiles.get(cacheKeys[i]);
+ diskTokens[i] = getDiskToken(params, cacheKeys[i]);
+
+ // Batch across common cache key.
+ for (int j = i + 1; j < len; j++) {
+ if (cacheKeys[j] == cacheKeys[i]) {
+ loadedTiles[j] = loadedTiles[i];
+ diskTokens[j] = diskTokens[i];
+ }
+ }
+ }
+
+ // Attempt to load tiles from disk.
+ for (int i = 0; i < len; i++) {
+ if (s2CellIds[i] == 0 || loadedTiles[i] != null) {
+ continue;
+ }
+
+ S2TileProto tile;
+ try (InputStream is = context.getApplicationContext().getAssets().open(
+ "geoid_height_map/tile-" + diskTokens[i] + ".pb")) {
+ tile = S2TileProto.parseFrom(is.readAllBytes());
+ }
+ mergeFromDiskTile(params, tile, cacheKeys, diskTokens, i, loadedTiles);
+ }
+
+ return s2CellId -> {
+ if (s2CellId == 0) {
+ return null;
+ }
+ long cacheKey = getCacheKey(params, s2CellId);
+ for (int i = 0; i < cacheKeys.length; i++) {
+ if (cacheKeys[i] == cacheKey) {
+ return loadedTiles[i];
+ }
+ }
+ return null;
+ };
+ }
+
+ private void mergeFromDiskTile(@NonNull MapParamsProto params, @NonNull S2TileProto diskTile,
+ @NonNull long[] cacheKeys, @NonNull String[] diskTokens, int diskTokenIndex,
+ @NonNull S2TileProto[] loadedTiles) throws IOException {
+ int len = cacheKeys.length;
+ int numMapCellsPerCacheTile = 1 << (2 * (params.mapS2Level - params.cacheTileS2Level));
+
+ // Reusable arrays.
+ long[] s2CellIds = new long[numMapCellsPerCacheTile];
+ double[] values = new double[numMapCellsPerCacheTile];
+
+ // Each cache key identifies a different sub-tile of the disk tile.
+ TileFunction diskTileFunction = s2CellId -> diskTile;
+ for (int i = diskTokenIndex; i < len; i++) {
+ if (!Objects.equals(diskTokens[i], diskTokens[diskTokenIndex])
+ || loadedTiles[i] != null) {
+ continue;
+ }
+
+ // Find all map cells within the current cache tile.
+ long s2CellId = S2CellIdUtils.getTraversalStart(cacheKeys[i], params.mapS2Level);
+ for (int j = 0; j < numMapCellsPerCacheTile; j++) {
+ s2CellIds[j] = s2CellId;
+ s2CellId = S2CellIdUtils.getTraversalNext(s2CellId);
+ }
+
+ if (!getUnitIntervalValues(params, diskTileFunction, s2CellIds, values)) {
+ throw new IOException("Corrupted disk tile of disk token: " + diskTokens[i]);
+ }
+
+ loadedTiles[i] = new S2TileProto();
+ loadedTiles[i].byteBuffer = new byte[numMapCellsPerCacheTile];
+ for (int j = 0; j < numMapCellsPerCacheTile; j++) {
+ loadedTiles[i].byteBuffer[j] = (byte) Math.round(values[j] * 0xFF);
+ }
+
+ // Batch across common cache key.
+ for (int j = i + 1; j < len; j++) {
+ if (cacheKeys[j] == cacheKeys[i]) {
+ loadedTiles[j] = loadedTiles[i];
+ }
+ }
+
+ // Side load into tile cache.
+ mCacheTiles.put(cacheKeys[i], loadedTiles[i]);
+ }
+ }
+
+ /** Defines a function-like object to retrieve tiles for map cells. */
+ private interface TileFunction {
+
+ @Nullable
+ S2TileProto getTile(long s2CellId);
+ }
+}
diff --git a/location/java/com/android/internal/location/altitude/S2CellIdUtils.java b/location/java/com/android/internal/location/altitude/S2CellIdUtils.java
new file mode 100644
index 000000000000..5f113877d416
--- /dev/null
+++ b/location/java/com/android/internal/location/altitude/S2CellIdUtils.java
@@ -0,0 +1,653 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.location.altitude;
+
+import android.annotation.NonNull;
+
+import java.util.Arrays;
+import java.util.Locale;
+
+/**
+ * Provides lightweight S2 cell ID utilities without traditional geometry dependencies.
+ *
+ * <p>See <a href="https://s2geometry.io/">the S2 Geometry Library website</a> for more details.
+ */
+public final class S2CellIdUtils {
+
+ /** The level of all leaf S2 cells. */
+ public static final int MAX_LEVEL = 30;
+
+ private static final int MAX_SIZE = 1 << MAX_LEVEL;
+ private static final double ONE_OVER_MAX_SIZE = 1.0 / MAX_SIZE;
+ private static final int NUM_FACES = 6;
+ private static final int POS_BITS = 2 * MAX_LEVEL + 1;
+ private static final int SWAP_MASK = 0x1;
+ private static final int LOOKUP_BITS = 4;
+ private static final int LOOKUP_MASK = (1 << LOOKUP_BITS) - 1;
+ private static final int INVERT_MASK = 0x2;
+ private static final int LEAF_MASK = 0x1;
+ private static final int[] LOOKUP_POS = new int[1 << (2 * LOOKUP_BITS + 2)];
+ private static final int[] LOOKUP_IJ = new int[1 << (2 * LOOKUP_BITS + 2)];
+ private static final int[] POS_TO_ORIENTATION = {SWAP_MASK, 0, 0, INVERT_MASK + SWAP_MASK};
+ private static final int[][] POS_TO_IJ =
+ {{0, 1, 3, 2}, {0, 2, 3, 1}, {3, 2, 0, 1}, {3, 1, 0, 2}};
+ private static final double UV_LIMIT = calculateUvLimit();
+ private static final UvTransform[] UV_TRANSFORMS = createUvTransforms();
+ private static final XyzTransform[] XYZ_TRANSFORMS = createXyzTransforms();
+
+ // Used to encode (i, j, o) coordinates into primitive longs.
+ private static final int I_SHIFT = 33;
+ private static final int J_SHIFT = 2;
+ private static final long J_MASK = (1L << 31) - 1;
+
+ static {
+ initLookupCells();
+ }
+
+ /** Prevents instantiation. */
+ private S2CellIdUtils() {
+ }
+
+ /**
+ * Returns the leaf S2 cell ID for the specified latitude and longitude, both measured in
+ * degrees.
+ */
+ public static long fromLatLngDegrees(double latDegrees, double lngDegrees) {
+ return fromLatLngRadians(Math.toRadians(latDegrees), Math.toRadians(lngDegrees));
+ }
+
+ /**
+ * Returns the ID of the parent of the specified S2 cell at the specified parent level.
+ * Behavior is undefined for invalid S2 cell IDs or parent levels not in
+ * [0, {@code getLevel(s2CellId)}[.
+ */
+ public static long getParent(long s2CellId, int level) {
+ long newLsb = getLowestOnBitForLevel(level);
+ return (s2CellId & -newLsb) | newLsb;
+ }
+
+ /**
+ * Inserts into {@code neighbors} the four S2 cell IDs corresponding to the neighboring
+ * cells adjacent across the specified cell's four edges. This array must be of minimum
+ * length four, and elements at the tail end of the array not corresponding to a neighbor
+ * are set to zero. A reference to this array is returned.
+ *
+ * <p>Inserts in the order of down, right, up, and left directions, in that order. All
+ * neighbors are guaranteed to be distinct.
+ */
+ public static void getEdgeNeighbors(long s2CellId, @NonNull long[] neighbors) {
+ int level = getLevel(s2CellId);
+ int size = levelToSizeIj(level);
+ int face = getFace(s2CellId);
+ long ijo = toIjo(s2CellId);
+ int i = ijoToI(ijo);
+ int j = ijoToJ(ijo);
+
+ int iPlusSize = i + size;
+ int iMinusSize = i - size;
+ int jPlusSize = j + size;
+ int jMinusSize = j - size;
+ boolean iPlusSizeLtMax = iPlusSize < MAX_SIZE;
+ boolean iMinusSizeGteZero = iMinusSize >= 0;
+ boolean jPlusSizeLtMax = jPlusSize < MAX_SIZE;
+ boolean jMinusSizeGteZero = jMinusSize >= 0;
+
+ int index = 0;
+ // Down direction.
+ neighbors[index++] = getParent(fromFijSame(face, i, jMinusSize, jMinusSizeGteZero),
+ level);
+ // Right direction.
+ neighbors[index++] = getParent(fromFijSame(face, iPlusSize, j, iPlusSizeLtMax), level);
+ // Up direction.
+ neighbors[index++] = getParent(fromFijSame(face, i, jPlusSize, jPlusSizeLtMax), level);
+ // Left direction.
+ neighbors[index++] = getParent(fromFijSame(face, iMinusSize, j, iMinusSizeGteZero),
+ level);
+
+ // Pad end of neighbor array with zeros.
+ Arrays.fill(neighbors, index, neighbors.length, 0);
+ }
+
+ /** Returns the "i" coordinate for the specified S2 cell. */
+ public static int getI(long s2CellId) {
+ return ijoToI(toIjo(s2CellId));
+ }
+
+ /** Returns the "j" coordinate for the specified S2 cell. */
+ public static int getJ(long s2CellId) {
+ return ijoToJ(toIjo(s2CellId));
+ }
+
+ /**
+ * Returns the leaf S2 cell ID for the specified latitude and longitude, both measured in
+ * radians.
+ */
+ private static long fromLatLngRadians(double latRadians, double lngRadians) {
+ double cosLat = Math.cos(latRadians);
+ double x = Math.cos(lngRadians) * cosLat;
+ double y = Math.sin(lngRadians) * cosLat;
+ double z = Math.sin(latRadians);
+ return fromXyz(x, y, z);
+ }
+
+ /**
+ * Returns the level of the specified S2 cell. The returned level is in [0, 30] for valid
+ * S2 cell IDs. Behavior is undefined for invalid S2 cell IDs.
+ */
+ static int getLevel(long s2CellId) {
+ if (isLeaf(s2CellId)) {
+ return MAX_LEVEL;
+ }
+ return MAX_LEVEL - (Long.numberOfTrailingZeros(s2CellId) >> 1);
+ }
+
+ /** Returns the lowest-numbered bit that is on for the specified S2 cell. */
+ static long getLowestOnBit(long s2CellId) {
+ return s2CellId & -s2CellId;
+ }
+
+ /** Returns the lowest-numbered bit that is on for any S2 cell on the specified level. */
+ static long getLowestOnBitForLevel(int level) {
+ return 1L << (2 * (MAX_LEVEL - level));
+ }
+
+ /**
+ * Returns the ID of the first S2 cell in a traversal of the children S2 cells at the specified
+ * level, in Hilbert curve order.
+ */
+ static long getTraversalStart(long s2CellId, int level) {
+ return s2CellId - getLowestOnBit(s2CellId) + getLowestOnBitForLevel(level);
+ }
+
+ /** Returns the ID of the next S2 cell at the same level along the Hilbert curve. */
+ static long getTraversalNext(long s2CellId) {
+ return s2CellId + (getLowestOnBit(s2CellId) << 1);
+ }
+
+ /**
+ * Encodes the S2 cell id to compact text strings suitable for display or indexing. Cells at
+ * lower levels (i.e., larger cells) are encoded into fewer characters.
+ */
+ @NonNull
+ static String getToken(long s2CellId) {
+ if (s2CellId == 0) {
+ return "X";
+ }
+
+ // Convert to a hex string with as many digits as necessary.
+ String hex = Long.toHexString(s2CellId).toLowerCase(Locale.US);
+ // Prefix 0s to get a length 16 string.
+ String padded = padStart(hex);
+ // Trim zeroes off the end.
+ return padded.replaceAll("0*$", "");
+ }
+
+ private static String padStart(String string) {
+ if (string.length() >= 16) {
+ return string;
+ }
+ return "0".repeat(16 - string.length()) + string;
+ }
+
+ /** Returns the leaf S2 cell ID of the specified (x, y, z) coordinate. */
+ private static long fromXyz(double x, double y, double z) {
+ int face = xyzToFace(x, y, z);
+ UvTransform uvTransform = UV_TRANSFORMS[face];
+ double u = uvTransform.xyzToU(x, y, z);
+ double v = uvTransform.xyzToV(x, y, z);
+ return fromFuv(face, u, v);
+ }
+
+ /** Returns the leaf S2 cell ID of the specified (face, u, v) coordinate. */
+ private static long fromFuv(int face, double u, double v) {
+ int i = uToI(u);
+ int j = vToJ(v);
+ return fromFij(face, i, j);
+ }
+
+ /** Returns the leaf S2 cell ID of the specified (face, i, j) coordinate. */
+ private static long fromFij(int face, int i, int j) {
+ int bits = (face & SWAP_MASK);
+ // Update most significant bits.
+ long msb = ((long) face) << (POS_BITS - 33);
+ for (int k = 7; k >= 4; --k) {
+ bits = lookupBits(i, j, k, bits);
+ msb = updateBits(msb, k, bits);
+ bits = maskBits(bits);
+ }
+ // Update least significant bits.
+ long lsb = 0;
+ for (int k = 3; k >= 0; --k) {
+ bits = lookupBits(i, j, k, bits);
+ lsb = updateBits(lsb, k, bits);
+ bits = maskBits(bits);
+ }
+ return (((msb << 32) + lsb) << 1) + 1;
+ }
+
+ private static long fromFijWrap(int face, int i, int j) {
+ double u = iToU(i);
+ double v = jToV(j);
+
+ XyzTransform xyzTransform = XYZ_TRANSFORMS[face];
+ double x = xyzTransform.uvToX(u, v);
+ double y = xyzTransform.uvToY(u, v);
+ double z = xyzTransform.uvToZ(u, v);
+
+ int newFace = xyzToFace(x, y, z);
+ UvTransform uvTransform = UV_TRANSFORMS[newFace];
+ double newU = uvTransform.xyzToU(x, y, z);
+ double newV = uvTransform.xyzToV(x, y, z);
+
+ int newI = uShiftIntoI(newU);
+ int newJ = vShiftIntoJ(newV);
+ return fromFij(newFace, newI, newJ);
+ }
+
+ private static long fromFijSame(int face, int i, int j, boolean isSameFace) {
+ if (isSameFace) {
+ return fromFij(face, i, j);
+ }
+ return fromFijWrap(face, i, j);
+ }
+
+ /**
+ * Returns the face associated with the specified (x, y, z) coordinate. For a coordinate
+ * on a face boundary, the returned face is arbitrary but repeatable.
+ */
+ private static int xyzToFace(double x, double y, double z) {
+ double absX = Math.abs(x);
+ double absY = Math.abs(y);
+ double absZ = Math.abs(z);
+ if (absX > absY) {
+ if (absX > absZ) {
+ return (x < 0) ? 3 : 0;
+ }
+ return (z < 0) ? 5 : 2;
+ }
+ if (absY > absZ) {
+ return (y < 0) ? 4 : 1;
+ }
+ return (z < 0) ? 5 : 2;
+ }
+
+ private static int uToI(double u) {
+ double s;
+ if (u >= 0) {
+ s = 0.5 * Math.sqrt(1 + 3 * u);
+ } else {
+ s = 1 - 0.5 * Math.sqrt(1 - 3 * u);
+ }
+ return Math.max(0, Math.min(MAX_SIZE - 1, (int) Math.round(MAX_SIZE * s - 0.5)));
+ }
+
+ private static int vToJ(double v) {
+ // Same calculation as uToI.
+ return uToI(v);
+ }
+
+ private static int lookupBits(int i, int j, int k, int bits) {
+ bits += ((i >> (k * LOOKUP_BITS)) & LOOKUP_MASK) << (LOOKUP_BITS + 2);
+ bits += ((j >> (k * LOOKUP_BITS)) & LOOKUP_MASK) << 2;
+ return LOOKUP_POS[bits];
+ }
+
+ private static long updateBits(long sb, int k, int bits) {
+ return sb | ((((long) bits) >> 2) << ((k & 0x3) * 2 * LOOKUP_BITS));
+ }
+
+ private static int maskBits(int bits) {
+ return bits & (SWAP_MASK | INVERT_MASK);
+ }
+
+ private static int getFace(long s2CellId) {
+ return (int) (s2CellId >>> POS_BITS);
+ }
+
+ private static boolean isLeaf(long s2CellId) {
+ return ((int) s2CellId & LEAF_MASK) != 0;
+ }
+
+ private static double iToU(int i) {
+ int satI = Math.max(-1, Math.min(MAX_SIZE, i));
+ return Math.max(
+ -UV_LIMIT,
+ Math.min(UV_LIMIT, ONE_OVER_MAX_SIZE * ((satI << 1) + 1 - MAX_SIZE)));
+ }
+
+ private static double jToV(int j) {
+ // Same calculation as iToU.
+ return iToU(j);
+ }
+
+ private static long toIjo(long s2CellId) {
+ int face = getFace(s2CellId);
+ int bits = face & SWAP_MASK;
+ int i = 0;
+ int j = 0;
+ for (int k = 7; k >= 0; --k) {
+ int nbits = (k == 7) ? (MAX_LEVEL - 7 * LOOKUP_BITS) : LOOKUP_BITS;
+ bits += ((int) (s2CellId >>> (k * 2 * LOOKUP_BITS + 1)) & ((1 << (2 * nbits))
+ - 1)) << 2;
+ bits = LOOKUP_IJ[bits];
+ i += (bits >> (LOOKUP_BITS + 2)) << (k * LOOKUP_BITS);
+ j += ((bits >> 2) & ((1 << LOOKUP_BITS) - 1)) << (k * LOOKUP_BITS);
+ bits &= (SWAP_MASK | INVERT_MASK);
+ }
+ int orientation =
+ ((getLowestOnBit(s2CellId) & 0x1111111111111110L) != 0) ? (bits ^ SWAP_MASK)
+ : bits;
+ return (((long) i) << I_SHIFT) | (((long) j) << J_SHIFT) | orientation;
+ }
+
+ private static int ijoToI(long ijo) {
+ return (int) (ijo >>> I_SHIFT);
+ }
+
+ private static int ijoToJ(long ijo) {
+ return (int) ((ijo >>> J_SHIFT) & J_MASK);
+ }
+
+ private static int uShiftIntoI(double u) {
+ double s = 0.5 * (u + 1);
+ return Math.max(0, Math.min(MAX_SIZE - 1, (int) Math.round(MAX_SIZE * s - 0.5)));
+ }
+
+ private static int vShiftIntoJ(double v) {
+ // Same calculation as uShiftIntoI.
+ return uShiftIntoI(v);
+ }
+
+ private static int levelToSizeIj(int level) {
+ return 1 << (MAX_LEVEL - level);
+ }
+
+ private static void initLookupCells() {
+ initLookupCell(0, 0, 0, 0, 0, 0);
+ initLookupCell(0, 0, 0, SWAP_MASK, 0, SWAP_MASK);
+ initLookupCell(0, 0, 0, INVERT_MASK, 0, INVERT_MASK);
+ initLookupCell(0, 0, 0, SWAP_MASK | INVERT_MASK, 0, SWAP_MASK | INVERT_MASK);
+ }
+
+ private static void initLookupCell(
+ int level, int i, int j, int origOrientation, int pos, int orientation) {
+ if (level == LOOKUP_BITS) {
+ int ij = (i << LOOKUP_BITS) + j;
+ LOOKUP_POS[(ij << 2) + origOrientation] = (pos << 2) + orientation;
+ LOOKUP_IJ[(pos << 2) + origOrientation] = (ij << 2) + orientation;
+ } else {
+ level++;
+ i <<= 1;
+ j <<= 1;
+ pos <<= 2;
+ for (int subPos = 0; subPos < 4; subPos++) {
+ int ij = POS_TO_IJ[orientation][subPos];
+ int orientationMask = POS_TO_ORIENTATION[subPos];
+ initLookupCell(
+ level,
+ i + (ij >>> 1),
+ j + (ij & 0x1),
+ origOrientation,
+ pos + subPos,
+ orientation ^ orientationMask);
+ }
+ }
+ }
+
+ private static double calculateUvLimit() {
+ double machEps = 1.0;
+ do {
+ machEps /= 2.0f;
+ } while ((1.0 + (machEps / 2.0)) != 1.0);
+ return 1.0 + machEps;
+ }
+
+ @NonNull
+ private static UvTransform[] createUvTransforms() {
+ UvTransform[] uvTransforms = new UvTransform[NUM_FACES];
+ uvTransforms[0] =
+ new UvTransform() {
+
+ @Override
+ public double xyzToU(double x, double y, double z) {
+ return y / x;
+ }
+
+ @Override
+ public double xyzToV(double x, double y, double z) {
+ return z / x;
+ }
+ };
+ uvTransforms[1] =
+ new UvTransform() {
+
+ @Override
+ public double xyzToU(double x, double y, double z) {
+ return -x / y;
+ }
+
+ @Override
+ public double xyzToV(double x, double y, double z) {
+ return z / y;
+ }
+ };
+ uvTransforms[2] =
+ new UvTransform() {
+
+ @Override
+ public double xyzToU(double x, double y, double z) {
+ return -x / z;
+ }
+
+ @Override
+ public double xyzToV(double x, double y, double z) {
+ return -y / z;
+ }
+ };
+ uvTransforms[3] =
+ new UvTransform() {
+
+ @Override
+ public double xyzToU(double x, double y, double z) {
+ return z / x;
+ }
+
+ @Override
+ public double xyzToV(double x, double y, double z) {
+ return y / x;
+ }
+ };
+ uvTransforms[4] =
+ new UvTransform() {
+
+ @Override
+ public double xyzToU(double x, double y, double z) {
+ return z / y;
+ }
+
+ @Override
+ public double xyzToV(double x, double y, double z) {
+ return -x / y;
+ }
+ };
+ uvTransforms[5] =
+ new UvTransform() {
+
+ @Override
+ public double xyzToU(double x, double y, double z) {
+ return -y / z;
+ }
+
+ @Override
+ public double xyzToV(double x, double y, double z) {
+ return -x / z;
+ }
+ };
+ return uvTransforms;
+ }
+
+ @NonNull
+ private static XyzTransform[] createXyzTransforms() {
+ XyzTransform[] xyzTransforms = new XyzTransform[NUM_FACES];
+ xyzTransforms[0] =
+ new XyzTransform() {
+
+ @Override
+ public double uvToX(double u, double v) {
+ return 1;
+ }
+
+ @Override
+ public double uvToY(double u, double v) {
+ return u;
+ }
+
+ @Override
+ public double uvToZ(double u, double v) {
+ return v;
+ }
+ };
+ xyzTransforms[1] =
+ new XyzTransform() {
+
+ @Override
+ public double uvToX(double u, double v) {
+ return -u;
+ }
+
+ @Override
+ public double uvToY(double u, double v) {
+ return 1;
+ }
+
+ @Override
+ public double uvToZ(double u, double v) {
+ return v;
+ }
+ };
+ xyzTransforms[2] =
+ new XyzTransform() {
+
+ @Override
+ public double uvToX(double u, double v) {
+ return -u;
+ }
+
+ @Override
+ public double uvToY(double u, double v) {
+ return -v;
+ }
+
+ @Override
+ public double uvToZ(double u, double v) {
+ return 1;
+ }
+ };
+ xyzTransforms[3] =
+ new XyzTransform() {
+
+ @Override
+ public double uvToX(double u, double v) {
+ return -1;
+ }
+
+ @Override
+ public double uvToY(double u, double v) {
+ return -v;
+ }
+
+ @Override
+ public double uvToZ(double u, double v) {
+ return -u;
+ }
+ };
+ xyzTransforms[4] =
+ new XyzTransform() {
+
+ @Override
+ public double uvToX(double u, double v) {
+ return v;
+ }
+
+ @Override
+ public double uvToY(double u, double v) {
+ return -1;
+ }
+
+ @Override
+ public double uvToZ(double u, double v) {
+ return -u;
+ }
+ };
+ xyzTransforms[5] =
+ new XyzTransform() {
+
+ @Override
+ public double uvToX(double u, double v) {
+ return v;
+ }
+
+ @Override
+ public double uvToY(double u, double v) {
+ return u;
+ }
+
+ @Override
+ public double uvToZ(double u, double v) {
+ return -1;
+ }
+ };
+ return xyzTransforms;
+ }
+
+ /**
+ * Transform from (x, y, z) coordinates to (u, v) coordinates, indexed by face. For a
+ * (x, y, z) coordinate within a face, each element of the resulting (u, v) coordinate
+ * should lie in the inclusive range [-1, 1], with the face center having a (u, v)
+ * coordinate equal to (0, 0).
+ */
+ private interface UvTransform {
+
+ /**
+ * Returns for the specified (x, y, z) coordinate the corresponding u-coordinate
+ * (which may lie outside the range [-1, 1]).
+ */
+ double xyzToU(double x, double y, double z);
+
+ /**
+ * Returns for the specified (x, y, z) coordinate the corresponding v-coordinate
+ * (which may lie outside the range [-1, 1]).
+ */
+ double xyzToV(double x, double y, double z);
+ }
+
+ /**
+ * Transform from (u, v) coordinates to (x, y, z) coordinates, indexed by face. The
+ * resulting vectors are not necessarily of unit length.
+ */
+ private interface XyzTransform {
+
+ /** Returns for the specified (u, v) coordinate the corresponding x-coordinate. */
+ double uvToX(double u, double v);
+
+ /** Returns for the specified (u, v) coordinate the corresponding y-coordinate. */
+ double uvToY(double u, double v);
+
+ /** Returns for the specified (u, v) coordinate the corresponding z-coordinate. */
+ double uvToZ(double u, double v);
+ }
+}
diff --git a/media/java/android/media/AudioHalVersionInfo.aidl b/media/java/android/media/AudioHalVersionInfo.aidl
new file mode 100644
index 000000000000..a83f8c81fb21
--- /dev/null
+++ b/media/java/android/media/AudioHalVersionInfo.aidl
@@ -0,0 +1,18 @@
+/* Copyright 2022, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.media;
+
+parcelable AudioHalVersionInfo;
diff --git a/media/java/android/media/AudioHalVersionInfo.java b/media/java/android/media/AudioHalVersionInfo.java
new file mode 100644
index 000000000000..985a7584ffe2
--- /dev/null
+++ b/media/java/android/media/AudioHalVersionInfo.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.TestApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.List;
+
+/**
+ * Defines the audio HAL version.
+ *
+ * @hide
+ */
+@TestApi
+public final class AudioHalVersionInfo implements Parcelable, Comparable<AudioHalVersionInfo> {
+ /**
+ * Indicate the audio HAL is implemented with HIDL (HAL interface definition language).
+ *
+ * @see <a href="https://source.android.com/docs/core/architecture/hidl/">HIDL</a>
+ * <p>The value of AUDIO_HAL_TYPE_HIDL should match the value of {@link
+ * android.media.AudioHalVersion.Type#HIDL}.
+ */
+ public static final int AUDIO_HAL_TYPE_HIDL = 0;
+
+ /**
+ * Indicate the audio HAL is implemented with AIDL (Android Interface Definition Language).
+ *
+ * @see <a href="https://source.android.com/docs/core/architecture/aidl/">AIDL</a>
+ * <p>The value of AUDIO_HAL_TYPE_AIDL should match the value of {@link
+ * android.media.AudioHalVersion.Type#AIDL}.
+ */
+ public static final int AUDIO_HAL_TYPE_AIDL = 1;
+
+ /** @hide */
+ @IntDef(
+ flag = false,
+ prefix = "AUDIO_HAL_TYPE_",
+ value = {AUDIO_HAL_TYPE_HIDL, AUDIO_HAL_TYPE_AIDL})
+ public @interface AudioHalType {}
+
+ /** AudioHalVersionInfo object of all valid Audio HAL versions. */
+ public static final @NonNull AudioHalVersionInfo AIDL_1_0 =
+ new AudioHalVersionInfo(AUDIO_HAL_TYPE_AIDL, 1 /* major */, 0 /* minor */);
+
+ public static final @NonNull AudioHalVersionInfo HIDL_7_1 =
+ new AudioHalVersionInfo(AUDIO_HAL_TYPE_HIDL, 7 /* major */, 1 /* minor */);
+ public static final @NonNull AudioHalVersionInfo HIDL_7_0 =
+ new AudioHalVersionInfo(AUDIO_HAL_TYPE_HIDL, 7 /* major */, 0 /* minor */);
+ public static final @NonNull AudioHalVersionInfo HIDL_6_0 =
+ new AudioHalVersionInfo(AUDIO_HAL_TYPE_HIDL, 6 /* major */, 0 /* minor */);
+ public static final @NonNull AudioHalVersionInfo HIDL_5_0 =
+ new AudioHalVersionInfo(AUDIO_HAL_TYPE_HIDL, 5 /* major */, 0 /* minor */);
+ public static final @NonNull AudioHalVersionInfo HIDL_4_0 =
+ new AudioHalVersionInfo(AUDIO_HAL_TYPE_HIDL, 4 /* major */, 0 /* minor */);
+ public static final @NonNull AudioHalVersionInfo HIDL_2_0 =
+ new AudioHalVersionInfo(AUDIO_HAL_TYPE_HIDL, 2 /* major */, 0 /* minor */);
+
+ /**
+ * List of all valid Audio HAL versions. This list need to be in sync with sAudioHALVersions
+ * defined in frameworks/av/media/libaudiohal/FactoryHalHidl.cpp.
+ */
+ // TODO: add AIDL_1_0 with sAudioHALVersions.
+ public static final @NonNull List<AudioHalVersionInfo> VERSIONS =
+ List.of(HIDL_7_1, HIDL_7_0, HIDL_6_0, HIDL_5_0, HIDL_4_0);
+
+ private static final String TAG = "AudioHalVersionInfo";
+ private AudioHalVersion mHalVersion = new AudioHalVersion();
+
+ public @AudioHalType int getHalType() {
+ return mHalVersion.type;
+ }
+
+ public int getMajorVersion() {
+ return mHalVersion.major;
+ }
+
+ public int getMinorVersion() {
+ return mHalVersion.minor;
+ }
+
+ /** String representative of AudioHalVersion.Type */
+ private static @NonNull String typeToString(@AudioHalType int type) {
+ if (type == AudioHalVersion.Type.HIDL) {
+ return "HIDL";
+ } else if (type == AudioHalVersion.Type.AIDL) {
+ return "AIDL";
+ } else {
+ return "INVALID";
+ }
+ }
+
+ /** String representative of type, major and minor */
+ private static @NonNull String toString(@AudioHalType int type, int major, int minor) {
+ return typeToString(type) + ":" + Integer.toString(major) + "." + Integer.toString(minor);
+ }
+
+ private AudioHalVersionInfo(@AudioHalType int type, int major, int minor) {
+ mHalVersion.type = type;
+ mHalVersion.major = major;
+ mHalVersion.minor = minor;
+ }
+
+ private AudioHalVersionInfo(Parcel in) {
+ mHalVersion = in.readTypedObject(AudioHalVersion.CREATOR);
+ }
+
+ /** String representative of this (AudioHalVersionInfo) object */
+ @Override
+ public String toString() {
+ return toString(mHalVersion.type, mHalVersion.major, mHalVersion.minor);
+ }
+
+ /**
+ * Compare two HAL versions by comparing their index in VERSIONS.
+ *
+ * <p>Normally all AudioHalVersionInfo object to compare should exist in the VERSIONS list. If
+ * both candidates exist in the VERSIONS list, smaller index means newer. Any candidate not
+ * exist in the VERSIONS list will be considered to be oldest version.
+ *
+ * @return 0 if the HAL version is the same as the other HAL version. Positive if the HAL
+ * version is newer than the other HAL version. Negative if the HAL version is older than
+ * the other version.
+ */
+ @Override
+ public int compareTo(@NonNull AudioHalVersionInfo other) {
+ int indexOther = VERSIONS.indexOf(other);
+ int indexThis = VERSIONS.indexOf(this);
+ if (indexThis < 0 || indexOther < 0) {
+ return indexThis - indexOther;
+ }
+ return indexOther - indexThis;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull android.os.Parcel out, int flag) {
+ out.writeTypedObject(mHalVersion, flag);
+ }
+
+ public static final @NonNull Parcelable.Creator<AudioHalVersionInfo> CREATOR =
+ new Parcelable.Creator<AudioHalVersionInfo>() {
+ @Override
+ public AudioHalVersionInfo createFromParcel(@NonNull Parcel in) {
+ return new AudioHalVersionInfo(in);
+ }
+
+ @Override
+ public AudioHalVersionInfo[] newArray(int size) {
+ return new AudioHalVersionInfo[size];
+ }
+ };
+}
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 9c5313aa3261..ae0d45ffca24 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -8502,13 +8502,14 @@ public class AudioManager {
}
/**
- * Returns the audio HAL version in the form MAJOR.MINOR. If there is no audio HAL found, null
- * will be returned.
+ * Returns an {@link AudioHalVersionInfo} indicating the Audio Hal Version. If there is no audio
+ * HAL found, null will be returned.
*
+ * @return @see @link #AudioHalVersionInfo The version of Audio HAL.
* @hide
*/
@TestApi
- public static @Nullable String getHalVersion() {
+ public static @Nullable AudioHalVersionInfo getHalVersion() {
try {
return getService().getHalVersion();
} catch (RemoteException e) {
diff --git a/media/java/android/media/AudioPresentation.java b/media/java/android/media/AudioPresentation.java
index 47358be3e926..05f3c5a3512b 100644
--- a/media/java/android/media/AudioPresentation.java
+++ b/media/java/android/media/AudioPresentation.java
@@ -54,7 +54,11 @@ public final class AudioPresentation {
private final int mProgramId;
private final ULocale mLanguage;
- /** @hide */
+ /**
+ * The ContentClassifier int definitions represent the AudioPresentation content
+ * classifier (as per TS 103 190-1 v1.2.1 4.3.3.8.1)
+ * @hide
+ */
@IntDef(
value = {
CONTENT_UNKNOWN,
@@ -67,11 +71,6 @@ public final class AudioPresentation {
CONTENT_EMERGENCY,
CONTENT_VOICEOVER,
})
-
- /**
- * The ContentClassifier int definitions represent the AudioPresentation content
- * classifier (as per TS 103 190-1 v1.2.1 4.3.3.8.1)
- */
@Retention(RetentionPolicy.SOURCE)
public @interface ContentClassifier {}
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 2e766d59fbb3..ee453a4541c2 100755..100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -22,6 +22,7 @@ import android.media.AudioAttributes;
import android.media.AudioDeviceAttributes;
import android.media.AudioFormat;
import android.media.AudioFocusInfo;
+import android.media.AudioHalVersionInfo;
import android.media.AudioPlaybackConfiguration;
import android.media.AudioRecordingConfiguration;
import android.media.AudioRoutesInfo;
@@ -571,5 +572,5 @@ interface IAudioService {
in AudioDeviceAttributes device, in List<VolumeInfo> volumes,
boolean handlesvolumeAdjustment);
- String getHalVersion();
+ AudioHalVersionInfo getHalVersion();
}
diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java
index bf30c5032132..30d90a8814d8 100644
--- a/media/java/android/media/MediaCodecInfo.java
+++ b/media/java/android/media/MediaCodecInfo.java
@@ -4106,6 +4106,22 @@ public final class MediaCodecInfo {
public static final int AV1Level72 = 0x400000;
public static final int AV1Level73 = 0x800000;
+ /** DTS codec profile for DTS HRA. */
+ @SuppressLint("AllUpper")
+ public static final int DTS_HDProfileHRA = 0x1;
+ /** DTS codec profile for DTS Express. */
+ @SuppressLint("AllUpper")
+ public static final int DTS_HDProfileLBR = 0x2;
+ /** DTS codec profile for DTS-HD Master Audio */
+ @SuppressLint("AllUpper")
+ public static final int DTS_HDProfileMA = 0x4;
+ /** DTS codec profile for DTS:X Profile 1 */
+ @SuppressLint("AllUpper")
+ public static final int DTS_UHDProfileP1 = 0x1;
+ /** DTS codec profile for DTS:X Profile 2 */
+ @SuppressLint("AllUpper")
+ public static final int DTS_UHDProfileP2 = 0x2;
+
/**
* The profile of the media content. Depending on the type of media this can be
* one of the profile values defined in this class.
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index 7786f619840a..aea6bcb8f89f 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -76,13 +76,7 @@ public final class MediaRouter2Manager {
private static MediaRouter2Manager sInstance;
private final MediaSessionManager mMediaSessionManager;
-
- final String mPackageName;
-
- private final Context mContext;
-
private final Client mClient;
-
private final IMediaRouterService mMediaRouterService;
private final AtomicInteger mScanRequestCount = new AtomicInteger(/* initialValue= */ 0);
final Handler mHandler;
@@ -120,16 +114,14 @@ public final class MediaRouter2Manager {
}
private MediaRouter2Manager(Context context) {
- mContext = context.getApplicationContext();
mMediaRouterService = IMediaRouterService.Stub.asInterface(
ServiceManager.getService(Context.MEDIA_ROUTER_SERVICE));
mMediaSessionManager = (MediaSessionManager) context
.getSystemService(Context.MEDIA_SESSION_SERVICE);
- mPackageName = mContext.getPackageName();
mHandler = new Handler(context.getMainLooper());
mClient = new Client();
try {
- mMediaRouterService.registerManager(mClient, mPackageName);
+ mMediaRouterService.registerManager(mClient, context.getPackageName());
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
diff --git a/media/java/android/media/RingtoneManager.java b/media/java/android/media/RingtoneManager.java
index f15f4438d671..1e270b14a5e1 100644
--- a/media/java/android/media/RingtoneManager.java
+++ b/media/java/android/media/RingtoneManager.java
@@ -826,6 +826,19 @@ public class RingtoneManager {
if(!isInternalRingtoneUri(ringtoneUri)) {
ringtoneUri = ContentProvider.maybeAddUserId(ringtoneUri, context.getUserId());
}
+
+ final String mimeType = resolver.getType(ringtoneUri);
+ if (mimeType == null) {
+ Log.e(TAG, "setActualDefaultRingtoneUri for URI:" + ringtoneUri
+ + " ignored: failure to find mimeType (no access from this context?)");
+ return;
+ }
+ if (!(mimeType.startsWith("audio/") || mimeType.equals("application/ogg"))) {
+ Log.e(TAG, "setActualDefaultRingtoneUri for URI:" + ringtoneUri
+ + " ignored: associated mimeType:" + mimeType + " is not an audio type");
+ return;
+ }
+
Settings.System.putStringForUser(resolver, setting,
ringtoneUri != null ? ringtoneUri.toString() : null, context.getUserId());
diff --git a/media/tests/AudioPolicyTest/AndroidManifest.xml b/media/tests/AudioPolicyTest/AndroidManifest.xml
index f696735eb62d..5c911b135a5d 100644
--- a/media/tests/AudioPolicyTest/AndroidManifest.xml
+++ b/media/tests/AudioPolicyTest/AndroidManifest.xml
@@ -24,13 +24,22 @@
<application>
<uses-library android:name="android.test.runner" />
- <activity android:label="@string/app_name" android:name="AudioPolicyTestActivity"
+ <activity android:label="@string/app_name" android:name="AudioVolumeTestActivity"
android:screenOrientation="landscape" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
+ <activity android:label="@string/app_name" android:name="AudioPolicyDeathTestActivity"
+ android:screenOrientation="landscape"
+ android:process=":AudioPolicyDeathTestActivityProcess"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioPolicyDeathTest.java b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioPolicyDeathTest.java
new file mode 100644
index 000000000000..841804b02354
--- /dev/null
+++ b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioPolicyDeathTest.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.audiopolicytest;
+
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.media.AudioAttributes;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.AudioTrack;
+import android.platform.test.annotations.Presubmit;
+import android.util.Log;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class AudioPolicyDeathTest {
+ private static final String TAG = "AudioPolicyDeathTest";
+
+ private static final int SAMPLE_RATE = 48000;
+ private static final int PLAYBACK_TIME_MS = 2000;
+
+ private static final IntentFilter AUDIO_NOISY_INTENT_FILTER =
+ new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
+
+ private class MyBroadcastReceiver extends BroadcastReceiver {
+ private boolean mReceived = false;
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (AudioManager.ACTION_AUDIO_BECOMING_NOISY.equals(intent.getAction())) {
+ synchronized (this) {
+ mReceived = true;
+ notify();
+ }
+ }
+ }
+
+ public synchronized boolean received() {
+ return mReceived;
+ }
+ }
+ private final MyBroadcastReceiver mReceiver = new MyBroadcastReceiver();
+
+ private Context mContext;
+
+ @Before
+ public void setUp() {
+ mContext = getApplicationContext();
+ assertEquals(PackageManager.PERMISSION_GRANTED,
+ mContext.checkSelfPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING));
+ }
+
+ //-----------------------------------------------------------------
+ // Tests that an AUDIO_BECOMING_NOISY intent is broadcast when an app having registered
+ // a dynamic audio policy that intercepts an active media playback dies
+ //-----------------------------------------------------------------
+ @Test
+ public void testPolicyClientDeathSendBecomingNoisyIntent() {
+ mContext.registerReceiver(mReceiver, AUDIO_NOISY_INTENT_FILTER);
+
+ // Launch process registering a dynamic auido policy and dying after PLAYBACK_TIME_MS/2 ms
+ Intent intent = new Intent(mContext, AudioPolicyDeathTestActivity.class);
+ intent.putExtra("captureDurationMs", PLAYBACK_TIME_MS / 2);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ mContext.startActivity(intent);
+
+ AudioTrack track = createAudioTrack();
+ track.play();
+ synchronized (mReceiver) {
+ long startTimeMs = System.currentTimeMillis();
+ long elapsedTimeMs = 0;
+ while (elapsedTimeMs < PLAYBACK_TIME_MS && !mReceiver.received()) {
+ try {
+ mReceiver.wait(PLAYBACK_TIME_MS - elapsedTimeMs);
+ } catch (InterruptedException e) {
+ Log.w(TAG, "wait interrupted");
+ }
+ elapsedTimeMs = System.currentTimeMillis() - startTimeMs;
+ }
+ }
+
+ track.stop();
+ track.release();
+
+ assertTrue(mReceiver.received());
+ }
+
+ private AudioTrack createAudioTrack() {
+ AudioFormat format = new AudioFormat.Builder()
+ .setChannelMask(AudioFormat.CHANNEL_OUT_STEREO)
+ .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+ .setSampleRate(SAMPLE_RATE)
+ .build();
+
+ short[] data = new short[PLAYBACK_TIME_MS * SAMPLE_RATE * format.getChannelCount() / 1000];
+ AudioAttributes attributes =
+ new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).build();
+
+ AudioTrack track = new AudioTrack(attributes, format, data.length,
+ AudioTrack.MODE_STATIC, AudioManager.AUDIO_SESSION_ID_GENERATE);
+ track.write(data, 0, data.length);
+
+ return track;
+ }
+}
diff --git a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioPolicyDeathTestActivity.java b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioPolicyDeathTestActivity.java
new file mode 100644
index 000000000000..957e719ab71f
--- /dev/null
+++ b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioPolicyDeathTestActivity.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.audiopolicytest;
+
+import android.app.Activity;
+import android.media.AudioAttributes;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.AudioRecord;
+import android.media.audiopolicy.AudioMix;
+import android.media.audiopolicy.AudioMixingRule;
+import android.media.audiopolicy.AudioPolicy;
+import android.os.Bundle;
+import android.os.Looper;
+import android.util.Log;
+
+// This activity will register a dynamic audio policy to intercept media playback and launch
+// a thread that will capture audio from the policy mix and crash after the time indicated by
+// intent extra "captureDurationMs" has elapsed
+public class AudioPolicyDeathTestActivity extends Activity {
+ private static final String TAG = "AudioPolicyDeathTestActivity";
+
+ private static final int SAMPLE_RATE = 48000;
+ private static final int RECORD_TIME_MS = 1000;
+
+ private AudioManager mAudioManager = null;
+ private AudioPolicy mAudioPolicy = null;
+
+ public AudioPolicyDeathTestActivity() {
+ }
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ mAudioManager = getApplicationContext().getSystemService(AudioManager.class);
+
+ AudioAttributes attributes = new AudioAttributes.Builder()
+ .setUsage(AudioAttributes.USAGE_MEDIA).build();
+ AudioMixingRule.Builder audioMixingRuleBuilder = new AudioMixingRule.Builder()
+ .addRule(attributes, AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE);
+
+ AudioFormat audioFormat = new AudioFormat.Builder()
+ .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+ .setChannelMask(AudioFormat.CHANNEL_OUT_STEREO)
+ .setSampleRate(SAMPLE_RATE)
+ .build();
+
+ AudioMix audioMix = new AudioMix.Builder(audioMixingRuleBuilder.build())
+ .setFormat(audioFormat)
+ .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK)
+ .build();
+
+ AudioPolicy.Builder audioPolicyBuilder = new AudioPolicy.Builder(getApplicationContext());
+ audioPolicyBuilder.addMix(audioMix)
+ .setLooper(Looper.getMainLooper());
+ mAudioPolicy = audioPolicyBuilder.build();
+
+ int result = mAudioManager.registerAudioPolicy(mAudioPolicy);
+ if (result != AudioManager.SUCCESS) {
+ Log.w(TAG, "registerAudioPolicy failed, status: " + result);
+ return;
+ }
+ AudioRecord audioRecord = mAudioPolicy.createAudioRecordSink(audioMix);
+ if (audioRecord == null) {
+ Log.w(TAG, "AudioRecord creation failed");
+ return;
+ }
+
+ int captureDurationMs = getIntent().getIntExtra("captureDurationMs", RECORD_TIME_MS);
+ AudioCapturingThread thread = new AudioCapturingThread(audioRecord, captureDurationMs);
+ thread.start();
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ if (mAudioManager != null && mAudioPolicy != null) {
+ mAudioManager.unregisterAudioPolicy(mAudioPolicy);
+ }
+ }
+
+ // A thread that captures audio from the supplied AudioRecord and crashes after the supplied
+ // duration has elapsed
+ private static class AudioCapturingThread extends Thread {
+ private final AudioRecord mAudioRecord;
+ private final int mDurationMs;
+
+ AudioCapturingThread(AudioRecord record, int durationMs) {
+ super();
+ mAudioRecord = record;
+ mDurationMs = durationMs;
+ }
+
+ @Override
+ @SuppressWarnings("ConstantOverflow")
+ public void run() {
+ int samplesLeft = mDurationMs * SAMPLE_RATE * mAudioRecord.getChannelCount() / 1000;
+ short[] readBuffer = new short[samplesLeft / 10];
+ mAudioRecord.startRecording();
+ long startTimeMs = System.currentTimeMillis();
+ long elapsedTimeMs = 0;
+ do {
+ int read = readBuffer.length < samplesLeft ? readBuffer.length : samplesLeft;
+ read = mAudioRecord.read(readBuffer, 0, read);
+ elapsedTimeMs = System.currentTimeMillis() - startTimeMs;
+ if (read < 0) {
+ Log.w(TAG, "read error: " + read);
+ break;
+ }
+ samplesLeft -= read;
+ } while (elapsedTimeMs < mDurationMs && samplesLeft > 0);
+
+ // force process to crash
+ int i = 1 / 0;
+ }
+ }
+}
diff --git a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioPolicyTestActivity.java b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioVolumeTestActivity.java
index e31c01aae31e..8f61815fe5e6 100644
--- a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioPolicyTestActivity.java
+++ b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioVolumeTestActivity.java
@@ -19,9 +19,9 @@ package com.android.audiopolicytest;
import android.app.Activity;
import android.os.Bundle;
-public class AudioPolicyTestActivity extends Activity {
+public class AudioVolumeTestActivity extends Activity {
- public AudioPolicyTestActivity() {
+ public AudioVolumeTestActivity() {
}
/** Called when the activity is first created. */
diff --git a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioVolumesTestRule.java b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioVolumesTestRule.java
index fc3b198c4cad..c6ec7a615933 100644
--- a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioVolumesTestRule.java
+++ b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioVolumesTestRule.java
@@ -100,7 +100,7 @@ final class AudioVolumesTestRule extends ExternalResource {
@Before
public void setUp() throws Exception {
- ActivityScenario.launch(AudioPolicyTestActivity.class);
+ ActivityScenario.launch(AudioVolumeTestActivity.class);
mContext = getApplicationContext();
mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
diff --git a/native/android/Android.bp b/native/android/Android.bp
index f1b1d79265de..254eb4494ed8 100644
--- a/native/android/Android.bp
+++ b/native/android/Android.bp
@@ -95,6 +95,7 @@ cc_library_shared {
"libpowermanager",
"android.hardware.configstore@1.0",
"android.hardware.configstore-utils",
+ "android.hardware.power-V4-ndk",
"libnativedisplay",
],
diff --git a/native/android/performance_hint.cpp b/native/android/performance_hint.cpp
index 40eb507a5213..9e97bd33ce9c 100644
--- a/native/android/performance_hint.cpp
+++ b/native/android/performance_hint.cpp
@@ -16,6 +16,7 @@
#define LOG_TAG "perf_hint"
+#include <aidl/android/hardware/power/SessionHint.h>
#include <android/os/IHintManager.h>
#include <android/os/IHintSession.h>
#include <android/performance_hint.h>
@@ -25,14 +26,21 @@
#include <performance_hint_private.h>
#include <utils/SystemClock.h>
+#include <chrono>
#include <utility>
#include <vector>
using namespace android;
using namespace android::os;
+using namespace std::chrono_literals;
+
+using AidlSessionHint = aidl::android::hardware::power::SessionHint;
+
struct APerformanceHintSession;
+constexpr int64_t SEND_HINT_TIMEOUT = std::chrono::nanoseconds(100ms).count();
+
struct APerformanceHintManager {
public:
static APerformanceHintManager* getInstance();
@@ -75,6 +83,8 @@ private:
int64_t mFirstTargetMetTimestamp;
// Last target hit timestamp
int64_t mLastTargetMetTimestamp;
+ // Last hint reported from sendHint indexed by hint value
+ std::vector<int64_t> mLastHintSentTimestamp;
// Cached samples
std::vector<int64_t> mActualDurationsNanos;
std::vector<int64_t> mTimestampsNanos;
@@ -147,7 +157,12 @@ APerformanceHintSession::APerformanceHintSession(sp<IHintSession> session,
mPreferredRateNanos(preferredRateNanos),
mTargetDurationNanos(targetDurationNanos),
mFirstTargetMetTimestamp(0),
- mLastTargetMetTimestamp(0) {}
+ mLastTargetMetTimestamp(0) {
+ const std::vector<AidlSessionHint> sessionHintRange{ndk::enum_range<AidlSessionHint>().begin(),
+ ndk::enum_range<AidlSessionHint>().end()};
+
+ mLastHintSentTimestamp = std::vector<int64_t>(sessionHintRange.size(), 0);
+}
APerformanceHintSession::~APerformanceHintSession() {
binder::Status ret = mHintSession->close();
@@ -224,10 +239,16 @@ int APerformanceHintSession::reportActualWorkDuration(int64_t actualDurationNano
}
int APerformanceHintSession::sendHint(int32_t hint) {
- if (hint < 0) {
- ALOGE("%s: session hint value must be greater than zero", __FUNCTION__);
+ if (hint < 0 || hint >= static_cast<int32_t>(mLastHintSentTimestamp.size())) {
+ ALOGE("%s: invalid session hint %d", __FUNCTION__, hint);
return EINVAL;
}
+ int64_t now = elapsedRealtimeNano();
+
+ // Limit sendHint to a pre-detemined rate for safety
+ if (now < (mLastHintSentTimestamp[hint] + SEND_HINT_TIMEOUT)) {
+ return 0;
+ }
binder::Status ret = mHintSession->sendHint(hint);
@@ -235,6 +256,7 @@ int APerformanceHintSession::sendHint(int32_t hint) {
ALOGE("%s: HintSession sendHint failed: %s", __FUNCTION__, ret.exceptionMessage().c_str());
return EPIPE;
}
+ mLastHintSentTimestamp[hint] = now;
return 0;
}
diff --git a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
index 1881e60b0f16..0c2d3b6cd201 100644
--- a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
+++ b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
@@ -122,9 +122,16 @@ TEST_F(PerformanceHintTest, TestSession) {
result = APerformanceHint_reportActualWorkDuration(session, -1L);
EXPECT_EQ(EINVAL, result);
- // Send both valid and invalid session hints
int hintId = 2;
- EXPECT_CALL(*iSession, sendHint(Eq(2))).Times(Exactly(1));
+ EXPECT_CALL(*iSession, sendHint(Eq(hintId))).Times(Exactly(1));
+ result = APerformanceHint_sendHint(session, hintId);
+ EXPECT_EQ(0, result);
+ usleep(110000); // Sleep for longer than the update timeout.
+ EXPECT_CALL(*iSession, sendHint(Eq(hintId))).Times(Exactly(1));
+ result = APerformanceHint_sendHint(session, hintId);
+ EXPECT_EQ(0, result);
+ // Expect to get rate limited if we try to send faster than the limiter allows
+ EXPECT_CALL(*iSession, sendHint(Eq(hintId))).Times(Exactly(0));
result = APerformanceHint_sendHint(session, hintId);
EXPECT_EQ(0, result);
diff --git a/packages/CarrierDefaultApp/res/values-af/strings.xml b/packages/CarrierDefaultApp/res/values-af/strings.xml
index 51cb6c8466a5..3bc18ce1335e 100644
--- a/packages/CarrierDefaultApp/res/values-af/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-af/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"Die netwerk waarby jy probeer aansluit, het sekuriteitkwessies."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Byvoorbeeld, die aanmeldbladsy behoort dalk nie aan die organisasie wat gewys word nie."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Gaan in elk geval deur blaaier voort"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-am/strings.xml b/packages/CarrierDefaultApp/res/values-am/strings.xml
index d5d50ac0c72e..0efdbc420592 100644
--- a/packages/CarrierDefaultApp/res/values-am/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-am/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"ለመቀላቀል እየሞከሩ ያሉት አውታረ መረብ የደህንነት ችግሮች አሉበት።"</string>
<string name="ssl_error_example" msgid="6188711843183058764">"ለምሳሌ፣ የመግቢያ ገጹ የሚታየው ድርጅት ላይሆን ይችላል።"</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"ለማንኛውም በአሳሽ በኩል ይቀጥሉ"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-ar/strings.xml b/packages/CarrierDefaultApp/res/values-ar/strings.xml
index 0c67de771df3..cd979b2fd223 100644
--- a/packages/CarrierDefaultApp/res/values-ar/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-ar/strings.xml
@@ -16,4 +16,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"الشبكة التي تحاول الانضمام إليها بها مشاكل أمنية."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"على سبيل المثال، قد لا تنتمي صفحة تسجيل الدخول إلى المؤسسة المعروضة."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"المتابعة على أي حال عبر المتصفح"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-as/strings.xml b/packages/CarrierDefaultApp/res/values-as/strings.xml
index 4b36b06e35c7..fdafe2b41bf8 100644
--- a/packages/CarrierDefaultApp/res/values-as/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-as/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"আপুনি সংযোগ কৰিবলৈ বিচৰা নেটৱৰ্কটোত সুৰক্ষাজনিত সমস্যা আছে।"</string>
<string name="ssl_error_example" msgid="6188711843183058764">"উদাহৰণস্বৰূপে, আপোনাক দেখুওৱা লগ ইনৰ পৃষ্ঠাটো প্ৰতিষ্ঠানটোৰ নিজা নহ\'বও পাৰে।"</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"তথাপিও ব্ৰাউজাৰৰ জৰিয়তে অব্যাহত ৰাখক"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-az/strings.xml b/packages/CarrierDefaultApp/res/values-az/strings.xml
index d1af3c99b04a..32c3c1ce2b95 100644
--- a/packages/CarrierDefaultApp/res/values-az/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-az/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"Qoşulmaq istədiyiniz şəbəkənin təhlükəsizlik problemləri var."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Məsələn, giriş səhifəsi göstərilən təşkilata aid olmaya bilər."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Hər bir halda brazuer ilə davam edin"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-b+sr+Latn/strings.xml b/packages/CarrierDefaultApp/res/values-b+sr+Latn/strings.xml
index 5d557900c40d..932fc03a3178 100644
--- a/packages/CarrierDefaultApp/res/values-b+sr+Latn/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-b+sr+Latn/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"Mreža kojoj pokušavate da se pridružite ima bezbednosnih problema."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Na primer, stranica za prijavljivanje možda ne pripada prikazanoj organizaciji."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Ipak nastavi preko pregledača"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-be/strings.xml b/packages/CarrierDefaultApp/res/values-be/strings.xml
index 3ad85f2b37b5..20606f6bd71a 100644
--- a/packages/CarrierDefaultApp/res/values-be/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-be/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"У сеткі, да якой вы спрабуеце далучыцца, ёсць праблемы з бяспекай."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Напрыклад, старонка ўваходу можа не належаць указанай арганізацыі."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Усё роўна працягнуць праз браўзер"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-bg/strings.xml b/packages/CarrierDefaultApp/res/values-bg/strings.xml
index f5308f0541f8..46a9db519af4 100644
--- a/packages/CarrierDefaultApp/res/values-bg/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-bg/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"Мрежата, към която опитвате да се присъедините, има проблеми със сигурността."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Например страницата за вход може да не принадлежи на показаната организация."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Продължаване през браузър въпреки това"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-bn/strings.xml b/packages/CarrierDefaultApp/res/values-bn/strings.xml
index 448c42b39eca..0826ae1dc14f 100644
--- a/packages/CarrierDefaultApp/res/values-bn/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-bn/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"আপনি যে নেটওয়ার্কে যোগ দেওয়ার চেষ্টা করছেন সেটিতে নিরাপত্তাজনিত সমস্যা আছে।"</string>
<string name="ssl_error_example" msgid="6188711843183058764">"যেমন, লগ-ইন পৃষ্ঠাটি যে প্রতিষ্ঠানের পৃষ্ঠা বলে দেখানো আছে, আসলে তা নাও হতে পারে৷"</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"যাই হোক, ব্রাউজারের মাধ্যমে চালিয়ে যান"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-bs/strings.xml b/packages/CarrierDefaultApp/res/values-bs/strings.xml
index bc1ff339fb1a..e2bc342a41b5 100644
--- a/packages/CarrierDefaultApp/res/values-bs/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-bs/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"Mreža kojoj pokušavate pristupiti ima sigurnosnih problema."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Naprimjer, stranica za prijavljivanje možda ne pripada prikazanoj organizaciji."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Ipak nastavi preko preglednika"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-ca/strings.xml b/packages/CarrierDefaultApp/res/values-ca/strings.xml
index 66a8f37b1cf0..bdde56704f9a 100644
--- a/packages/CarrierDefaultApp/res/values-ca/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-ca/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"La xarxa a què et vols connectar té problemes de seguretat."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Per exemple, la pàgina d\'inici de sessió podria no pertànyer a l\'organització que es mostra."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Continua igualment mitjançant el navegador"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-cs/strings.xml b/packages/CarrierDefaultApp/res/values-cs/strings.xml
index 54318362b524..d5fdac963112 100644
--- a/packages/CarrierDefaultApp/res/values-cs/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-cs/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"Síť, ke které se pokoušíte připojit, má bezpečnostní problémy."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Přihlašovací stránka například nemusí patřit zobrazované organizaci."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Přesto pokračovat prostřednictvím prohlížeče"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-da/strings.xml b/packages/CarrierDefaultApp/res/values-da/strings.xml
index b21211706fad..8b2bb7c164e2 100644
--- a/packages/CarrierDefaultApp/res/values-da/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-da/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"Der er sikkerhedsproblemer på det netværk, du forsøger at logge ind på."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Det er f.eks. ikke sikkert, at loginsiden tilhører den anførte organisation."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Fortsæt alligevel via browseren"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-de/strings.xml b/packages/CarrierDefaultApp/res/values-de/strings.xml
index 95639ad51f59..21af41cc5a59 100644
--- a/packages/CarrierDefaultApp/res/values-de/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-de/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"Im Netzwerk, zu dem du eine Verbindung herstellen möchtest, liegen Sicherheitsprobleme vor."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Beispiel: Die Log-in-Seite gehört eventuell nicht zur angezeigten Organisation."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Trotzdem in einem Browser fortfahren"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-el/strings.xml b/packages/CarrierDefaultApp/res/values-el/strings.xml
index 016e68fc746a..75143143d497 100644
--- a/packages/CarrierDefaultApp/res/values-el/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-el/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"Παρουσιάζονται προβλήματα ασφάλειας στο δίκτυο στο οποίο προσπαθείτε να συνδεθείτε."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Για παράδειγμα, η σελίδα σύνδεσης ενδέχεται να μην ανήκει στον οργανισμό που εμφανίζεται."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Συνέχεια ούτως ή άλλως μέσω του προγράμματος περιήγησης"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-en-rAU/strings.xml b/packages/CarrierDefaultApp/res/values-en-rAU/strings.xml
index a925a30f3fc8..11d9437085a8 100644
--- a/packages/CarrierDefaultApp/res/values-en-rAU/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-en-rAU/strings.xml
@@ -14,4 +14,10 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"The network that you’re trying to join has security issues."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"For example, the login page might not belong to the organisation shown."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Continue anyway via browser"</string>
+ <string name="network_boost_notification_channel" msgid="5430986172506159199">"Network boost"</string>
+ <string name="network_boost_notification_title" msgid="8226368121348880044">"%s recommends a data boost"</string>
+ <string name="network_boost_notification_detail" msgid="3812434025544196192">"Buy a network boost for better performance"</string>
+ <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"Not now"</string>
+ <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"Manage"</string>
+ <string name="slice_purchase_app_label" msgid="915654761797446390">"Purchase a network boost."</string>
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-en-rCA/strings.xml b/packages/CarrierDefaultApp/res/values-en-rCA/strings.xml
index a925a30f3fc8..b5e53aef5582 100644
--- a/packages/CarrierDefaultApp/res/values-en-rCA/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-en-rCA/strings.xml
@@ -2,7 +2,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="5247871339820894594">"CarrierDefaultApp"</string>
- <string name="android_system_label" msgid="2797790869522345065">"Mobile Operator"</string>
+ <string name="android_system_label" msgid="2797790869522345065">"Mobile Carrier"</string>
<string name="portal_notification_id" msgid="5155057562457079297">"Mobile data has run out"</string>
<string name="no_data_notification_id" msgid="668400731803969521">"Your mobile data has been deactivated"</string>
<string name="portal_notification_detail" msgid="2295729385924660881">"Tap to visit the %s website"</string>
@@ -11,7 +11,13 @@
<string name="no_mobile_data_connection" msgid="544980465184147010">"Add data or roaming plan through %s"</string>
<string name="mobile_data_status_notification_channel_name" msgid="833999690121305708">"Mobile data status"</string>
<string name="action_bar_label" msgid="4290345990334377177">"Sign in to mobile network"</string>
- <string name="ssl_error_warning" msgid="3127935140338254180">"The network that you’re trying to join has security issues."</string>
- <string name="ssl_error_example" msgid="6188711843183058764">"For example, the login page might not belong to the organisation shown."</string>
+ <string name="ssl_error_warning" msgid="3127935140338254180">"The network you’re trying to join has security issues."</string>
+ <string name="ssl_error_example" msgid="6188711843183058764">"For example, the login page may not belong to the organization shown."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Continue anyway via browser"</string>
+ <string name="network_boost_notification_channel" msgid="5430986172506159199">"Network boost"</string>
+ <string name="network_boost_notification_title" msgid="8226368121348880044">"%s recommends a data boost"</string>
+ <string name="network_boost_notification_detail" msgid="3812434025544196192">"Buy a network boost for better performance"</string>
+ <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"Not now"</string>
+ <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"Manage"</string>
+ <string name="slice_purchase_app_label" msgid="915654761797446390">"Purchase a network boost."</string>
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-en-rGB/strings.xml b/packages/CarrierDefaultApp/res/values-en-rGB/strings.xml
index a925a30f3fc8..11d9437085a8 100644
--- a/packages/CarrierDefaultApp/res/values-en-rGB/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-en-rGB/strings.xml
@@ -14,4 +14,10 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"The network that you’re trying to join has security issues."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"For example, the login page might not belong to the organisation shown."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Continue anyway via browser"</string>
+ <string name="network_boost_notification_channel" msgid="5430986172506159199">"Network boost"</string>
+ <string name="network_boost_notification_title" msgid="8226368121348880044">"%s recommends a data boost"</string>
+ <string name="network_boost_notification_detail" msgid="3812434025544196192">"Buy a network boost for better performance"</string>
+ <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"Not now"</string>
+ <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"Manage"</string>
+ <string name="slice_purchase_app_label" msgid="915654761797446390">"Purchase a network boost."</string>
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-en-rIN/strings.xml b/packages/CarrierDefaultApp/res/values-en-rIN/strings.xml
index a925a30f3fc8..11d9437085a8 100644
--- a/packages/CarrierDefaultApp/res/values-en-rIN/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-en-rIN/strings.xml
@@ -14,4 +14,10 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"The network that you’re trying to join has security issues."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"For example, the login page might not belong to the organisation shown."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Continue anyway via browser"</string>
+ <string name="network_boost_notification_channel" msgid="5430986172506159199">"Network boost"</string>
+ <string name="network_boost_notification_title" msgid="8226368121348880044">"%s recommends a data boost"</string>
+ <string name="network_boost_notification_detail" msgid="3812434025544196192">"Buy a network boost for better performance"</string>
+ <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"Not now"</string>
+ <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"Manage"</string>
+ <string name="slice_purchase_app_label" msgid="915654761797446390">"Purchase a network boost."</string>
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-en-rXC/strings.xml b/packages/CarrierDefaultApp/res/values-en-rXC/strings.xml
index d7ae1cee29fd..f8a50e409a81 100644
--- a/packages/CarrierDefaultApp/res/values-en-rXC/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-en-rXC/strings.xml
@@ -14,4 +14,10 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‏‏‎‏‏‎‏‎‎‎‏‎‏‎‏‎‎‎‎‏‏‏‎‎‎‎‎‎‎‎‏‎‎‎‎‏‎‏‎‎‏‎‏‏‎‎‎‎‎‏‎‏‏‎‎‏‎‎‎The network you’re trying to join has security issues.‎‏‎‎‏‎"</string>
<string name="ssl_error_example" msgid="6188711843183058764">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‎‏‏‏‏‎‎‎‏‎‏‎‏‏‏‎‎‎‏‎‏‎‎‎‎‏‏‏‎‏‎‏‏‎‎‎‏‎‏‏‎‏‏‏‎‏‏‏‏‏‎‏‎‎‏‏‎‎‎For example, the login page may not belong to the organization shown.‎‏‎‎‏‎"</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‏‏‏‏‎‎‏‏‎‎‏‏‏‎‏‏‏‏‏‏‎‎‏‎‏‏‏‎‏‏‎‎‏‏‏‏‏‎‏‏‏‏‎‏‎‎‎‏‏‏‏‏‏‎‎‎‎‎‎Continue anyway via browser‎‏‎‎‏‎"</string>
+ <string name="network_boost_notification_channel" msgid="5430986172506159199">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‏‏‎‏‎‏‏‏‏‎‏‎‏‏‏‏‎‏‎‎‏‎‎‏‏‎‎‎‏‏‏‎‎‎‎‏‎‎‏‎‎‏‏‎‏‏‏‎‎‎‎‏‎‏‏‏‏‏‎Network boost‎‏‎‎‏‎"</string>
+ <string name="network_boost_notification_title" msgid="8226368121348880044">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‏‎‎‎‏‎‏‎‎‏‏‏‏‎‏‏‏‎‎‎‏‏‏‎‎‎‏‎‏‎‎‎‎‎‏‎‎‏‏‎‎‎‏‏‏‏‎‏‏‎‏‎‏‎‏‏‎‎‎%s recommends a data boost‎‏‎‎‏‎"</string>
+ <string name="network_boost_notification_detail" msgid="3812434025544196192">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‎‎‏‏‏‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‎‏‏‎‎‎‎‎‎‏‏‏‎‎‎‏‎‏‏‎‎‏‎‏‎‎‎‎‎‎‏‏‎‎‎‎‎‎Buy a network boost for better performance‎‏‎‎‏‎"</string>
+ <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‎‏‎‏‎‎‏‏‎‏‏‏‏‎‏‏‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‏‏‏‎‏‎‎‎‎‏‏‎‏‏‏‏‎‎‏‎‏‏‎‎‎‎‎‎Not now‎‏‎‎‏‎"</string>
+ <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‎‎‏‏‏‏‏‎‏‎‎‎‎‏‏‏‎‏‎‎‏‎‏‎‎‎‎‏‎‏‏‏‎‏‏‎‎‏‏‏‎‎‎‏‎‎‎‎‎‎‎‎‎‏‏‏‏‎‎Manage‎‏‎‎‏‎"</string>
+ <string name="slice_purchase_app_label" msgid="915654761797446390">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‎‎‏‎‏‏‎‏‎‏‎‎‎‎‏‏‏‏‎‎‏‎‎‏‏‏‏‎‎‎‏‏‎‏‏‏‎‏‎‏‎‎‏‏‏‎‏‎‏‎‏‏‏‏‎‏‏‎‎Purchase a network boost.‎‏‎‎‏‎"</string>
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-es-rUS/strings.xml b/packages/CarrierDefaultApp/res/values-es-rUS/strings.xml
index 0455603648c3..bddae48eb2d0 100644
--- a/packages/CarrierDefaultApp/res/values-es-rUS/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-es-rUS/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"La red a la que intentas conectarte tiene problemas de seguridad."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Por ejemplo, es posible que la página de acceso no pertenezca a la organización que aparece."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Continuar de todos modos desde el navegador"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-es/strings.xml b/packages/CarrierDefaultApp/res/values-es/strings.xml
index b5d038cf9292..dd56770857e3 100644
--- a/packages/CarrierDefaultApp/res/values-es/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-es/strings.xml
@@ -8,10 +8,22 @@
<string name="portal_notification_detail" msgid="2295729385924660881">"Toca para acceder al sitio web de %s"</string>
<string name="no_data_notification_detail" msgid="3112125343857014825">"Ponte en contacto con tu proveedor de servicios (%s)"</string>
<string name="no_mobile_data_connection_title" msgid="7449525772416200578">"Sin conexión de datos móviles"</string>
- <string name="no_mobile_data_connection" msgid="544980465184147010">"Añade un plan de datos o de itinerancia a través de %s"</string>
+ <string name="no_mobile_data_connection" msgid="544980465184147010">"Añade un plan de datos o de roaming a través de %s"</string>
<string name="mobile_data_status_notification_channel_name" msgid="833999690121305708">"Estado de la conexión de datos móviles"</string>
<string name="action_bar_label" msgid="4290345990334377177">"Iniciar sesión en una red móvil"</string>
<string name="ssl_error_warning" msgid="3127935140338254180">"La red a la que intentas unirte tiene problemas de seguridad."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Por ejemplo, es posible que la página de inicio de sesión no pertenezca a la organización mostrada."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Continuar de todos modos a través del navegador"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-et/strings.xml b/packages/CarrierDefaultApp/res/values-et/strings.xml
index 28cc9a93e935..8cdc2919e027 100644
--- a/packages/CarrierDefaultApp/res/values-et/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-et/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"Võrgul, millega üritate ühenduse luua, on turvaprobleeme."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Näiteks ei pruugi sisselogimisleht kuuluda kuvatavale organisatsioonile."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Jätka siiski brauseris"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-eu/strings.xml b/packages/CarrierDefaultApp/res/values-eu/strings.xml
index 04e641ff6c48..22565dca4cf6 100644
--- a/packages/CarrierDefaultApp/res/values-eu/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-eu/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"Erabili nahi duzun sareak segurtasun-arazoak ditu."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Adibidez, baliteke saioa hasteko orria adierazitako erakundearena ez izatea."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Jarraitu arakatzailearen bidez, halere"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-fa/strings.xml b/packages/CarrierDefaultApp/res/values-fa/strings.xml
index 5328a03d9646..ecb9930be98a 100644
--- a/packages/CarrierDefaultApp/res/values-fa/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-fa/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"شبکه‌ای که می‌خواهید به آن بپیوندید مشکلات امنیتی دارد."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"به عنوان مثال، صفحه ورود به سیستم ممکن است متعلق به سازمان نشان داده شده نباشد."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"درهر صورت ازطریق مرورگر ادامه یابد"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-fi/strings.xml b/packages/CarrierDefaultApp/res/values-fi/strings.xml
index d416c1d4d336..850f9db2a438 100644
--- a/packages/CarrierDefaultApp/res/values-fi/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-fi/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"Verkossa, johon yrität muodostaa yhteyttä, havaittiin turvallisuusongelmia."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Kirjautumissivu ei välttämättä kuulu näytetylle organisaatiolle."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Jatka selaimen kautta"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-fr-rCA/strings.xml b/packages/CarrierDefaultApp/res/values-fr-rCA/strings.xml
index 1b8c2623afc7..61fc2e4e1780 100644
--- a/packages/CarrierDefaultApp/res/values-fr-rCA/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-fr-rCA/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"Le réseau que vous essayez de joindre présente des problèmes de sécurité."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Par exemple, la page de connexion pourrait ne pas appartenir à l\'organisation représentée."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Continuer quand même dans un navigateur"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-fr/strings.xml b/packages/CarrierDefaultApp/res/values-fr/strings.xml
index 3ae95703a5e0..ef1857d925d0 100644
--- a/packages/CarrierDefaultApp/res/values-fr/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-fr/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"Le réseau auquel vous essayez de vous connecter présente des problèmes de sécurité."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Par exemple, la page de connexion peut ne pas appartenir à l\'organisation représentée."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Continuer quand même dans le navigateur"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-gl/strings.xml b/packages/CarrierDefaultApp/res/values-gl/strings.xml
index 4f199cab90fd..6dde8a87d3ea 100644
--- a/packages/CarrierDefaultApp/res/values-gl/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-gl/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"A rede á que tentas unirte ten problemas de seguranza."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Por exemplo, é posible que a páxina de inicio de sesión non pertenza á organización que se mostra."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Continuar igualmente co navegador"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-gu/strings.xml b/packages/CarrierDefaultApp/res/values-gu/strings.xml
index 57710d0393d2..473a0357f9af 100644
--- a/packages/CarrierDefaultApp/res/values-gu/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-gu/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"તમે જોડાવાનો પ્રયાસ કરી રહ્યા છો તે નેટવર્કમાં સુરક્ષા સંબંધી સમસ્યાઓ છે."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"ઉદાહરણ તરીકે, લોગિન પૃષ્ઠ બતાવવામાં આવેલી સંસ્થાનું ન પણ હોય."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"તો પણ બ્રાઉઝર મારફતે ચાલુ રાખો"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-hi/strings.xml b/packages/CarrierDefaultApp/res/values-hi/strings.xml
index b9d6f42e79a4..d878c1c55996 100644
--- a/packages/CarrierDefaultApp/res/values-hi/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-hi/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"आप जिस नेटवर्क में शामिल होने की कोशिश कर रहे हैं उसमें सुरक्षा से जुड़ी समस्‍याएं हैं."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"उदाहरण के लिए, हो सकता है कि लॉगिन पेज दिखाए गए संगठन का ना हो."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"ब्राउज़र के ज़रिए किसी भी तरह जारी रखें"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-hr/strings.xml b/packages/CarrierDefaultApp/res/values-hr/strings.xml
index 66531a7682e2..0da2280ba1a3 100644
--- a/packages/CarrierDefaultApp/res/values-hr/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-hr/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"Mreža kojoj se pokušavate pridružiti ima sigurnosne poteškoće."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Na primjer, stranica za prijavu možda ne pripada prikazanoj organizaciji."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Ipak nastavi putem preglednika"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-hu/strings.xml b/packages/CarrierDefaultApp/res/values-hu/strings.xml
index 4ae6ea67bb06..95c1db86fc23 100644
--- a/packages/CarrierDefaultApp/res/values-hu/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-hu/strings.xml
@@ -8,10 +8,22 @@
<string name="portal_notification_detail" msgid="2295729385924660881">"Koppintson a(z) %s webhely meglátogatásához"</string>
<string name="no_data_notification_detail" msgid="3112125343857014825">"Vegye fel a kapcsolatot szolgáltatójával (%s)"</string>
<string name="no_mobile_data_connection_title" msgid="7449525772416200578">"Nincs mobiladat-kapcsolat"</string>
- <string name="no_mobile_data_connection" msgid="544980465184147010">"Adjon hozzá előfizetést vagy barangolási csomagot a következőn keresztül: %s"</string>
+ <string name="no_mobile_data_connection" msgid="544980465184147010">"Adjon hozzá előfizetést vagy roamingcsomagot a következőn keresztül: %s"</string>
<string name="mobile_data_status_notification_channel_name" msgid="833999690121305708">"Mobiladat-állapot"</string>
<string name="action_bar_label" msgid="4290345990334377177">"Bejelentkezés a mobilhálózatra"</string>
<string name="ssl_error_warning" msgid="3127935140338254180">"Biztonsági problémák vannak azzal a hálózattal, amelyhez csatlakozni szeretne."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Például lehetséges, hogy a bejelentkezési oldal nem a megjelenített szervezethez tartozik."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Folytatás ennek ellenére böngészőn keresztül"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-hy/strings.xml b/packages/CarrierDefaultApp/res/values-hy/strings.xml
index 99398bc8f065..a846e044e7a4 100644
--- a/packages/CarrierDefaultApp/res/values-hy/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-hy/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"Ցանցը, որին փորձում եք միանալ, անվտանգության խնդիրներ ունի:"</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Օրինակ՝ մուտքի էջը կարող է ցուցադրված կազմակերպության էջը չլինել:"</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Շարունակել դիտարկիչի միջոցով"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-in/strings.xml b/packages/CarrierDefaultApp/res/values-in/strings.xml
index f48d31f56586..488ad09a0d32 100644
--- a/packages/CarrierDefaultApp/res/values-in/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-in/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"Jaringan yang ingin Anda masuki memiliki masalah keamanan."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Misalnya, halaman login mungkin bukan milik organisasi yang ditampilkan."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Tetap lanjutkan melalui browser"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-is/strings.xml b/packages/CarrierDefaultApp/res/values-is/strings.xml
index cdba5bea0add..ab4981d19579 100644
--- a/packages/CarrierDefaultApp/res/values-is/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-is/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"Öryggisvandamál eru á netinu sem þú ert að reyna að tengjast."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Til dæmis getur verið að innskráningarsíðan tilheyri ekki fyrirtækinu sem birtist."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Halda samt áfram í vafra"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-it/strings.xml b/packages/CarrierDefaultApp/res/values-it/strings.xml
index a62ae868a42e..95ea06084240 100644
--- a/packages/CarrierDefaultApp/res/values-it/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-it/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"La rete a cui stai tentando di accedere presenta problemi di sicurezza."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Ad esempio, la pagina di accesso potrebbe non appartenere all\'organizzazione indicata."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Continua comunque dal browser"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-iw/strings.xml b/packages/CarrierDefaultApp/res/values-iw/strings.xml
index 550936c6460a..263ed1ab4c3e 100644
--- a/packages/CarrierDefaultApp/res/values-iw/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-iw/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"יש בעיות אבטחה ברשת שאליה אתה מנסה להתחבר."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"לדוגמה, ייתכן שדף ההתחברות אינו שייך לארגון המוצג."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"המשך בכל זאת באמצעות דפדפן"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-ja/strings.xml b/packages/CarrierDefaultApp/res/values-ja/strings.xml
index e5977aebce63..3b22ae186196 100644
--- a/packages/CarrierDefaultApp/res/values-ja/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-ja/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"接続しようとしているネットワークにセキュリティの問題があります。"</string>
<string name="ssl_error_example" msgid="6188711843183058764">"たとえば、ログインページが表示されている組織に属していない可能性があります。"</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"ブラウザから続行"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-ka/strings.xml b/packages/CarrierDefaultApp/res/values-ka/strings.xml
index 91ae46d912eb..4b9cd387f0a1 100644
--- a/packages/CarrierDefaultApp/res/values-ka/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-ka/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"ქსელს, რომელთან დაკავშრებასაც ცდილობთ, უსაფრთხოების პრობლემები აქვს."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"მაგალითად, სისტემაში შესვლის გვერდი შეიძლება არ ეკუთვნოდეს ნაჩვენებ ორგანიზაციას."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"მაინც ბრაუზერში გაგრძელება"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-kk/strings.xml b/packages/CarrierDefaultApp/res/values-kk/strings.xml
index 0fb57bcdcf2a..fce8dd24a036 100644
--- a/packages/CarrierDefaultApp/res/values-kk/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-kk/strings.xml
@@ -3,15 +3,27 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="5247871339820894594">"CarrierDefaultApp"</string>
<string name="android_system_label" msgid="2797790869522345065">"Мобильдік байланыс операторы"</string>
- <string name="portal_notification_id" msgid="5155057562457079297">"Мобильдік деректер бітті"</string>
- <string name="no_data_notification_id" msgid="668400731803969521">"Мобильдік деректер өшірілді"</string>
+ <string name="portal_notification_id" msgid="5155057562457079297">"Мобильдік интернет бітті"</string>
+ <string name="no_data_notification_id" msgid="668400731803969521">"Мобильдік интернет өшірілді"</string>
<string name="portal_notification_detail" msgid="2295729385924660881">"%s вебсайтына кіру үшін түртіңіз"</string>
<string name="no_data_notification_detail" msgid="3112125343857014825">"Қызмет көрсетушіге (%s) хабарласыңыз"</string>
- <string name="no_mobile_data_connection_title" msgid="7449525772416200578">"Мобильдік деректер байланысы жоқ"</string>
+ <string name="no_mobile_data_connection_title" msgid="7449525772416200578">"Мобильдік интернет байланысы жоқ"</string>
<string name="no_mobile_data_connection" msgid="544980465184147010">"%s арқылы деректер не роуминг жоспарын енгізу"</string>
<string name="mobile_data_status_notification_channel_name" msgid="833999690121305708">"Мобильді деректер күйі"</string>
<string name="action_bar_label" msgid="4290345990334377177">"Мобильдік желіге тіркелу"</string>
<string name="ssl_error_warning" msgid="3127935140338254180">"Қосылайын деп жатқан желіңізде қауіпсіздік мәселелері бар."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Мысалы, кіру беті көрсетілген ұйымға тиесілі болмауы мүмкін."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Бәрібір браузер арқылы жалғастыру"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-km/strings.xml b/packages/CarrierDefaultApp/res/values-km/strings.xml
index 6ef106646103..983f09e74111 100644
--- a/packages/CarrierDefaultApp/res/values-km/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-km/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"បណ្តាញដែលអ្នកកំពុងព្យាយាមចូលមានបញ្ហាសុវត្ថិភាព។"</string>
<string name="ssl_error_example" msgid="6188711843183058764">"ឧទាហរណ៍៖ ទំព័រចូលនេះអាចនឹងមិនមែនជាកម្មសិទ្ធិរបស់ស្ថាប័នដែលបានបង្ហាញនេះទេ។"</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"យ៉ាងណាក៏ដោយនៅតែបន្តតាមរយៈកម្មវិធីរុករកតាមអ៊ីនធឺណិត"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-kn/strings.xml b/packages/CarrierDefaultApp/res/values-kn/strings.xml
index ea4b09a52189..86b29cece868 100644
--- a/packages/CarrierDefaultApp/res/values-kn/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-kn/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"ನೀವು ಸೇರಬೇಕೆಂದಿರುವ ನೆಟ್‌ವರ್ಕ್, ಭದ್ರತೆ ಸಮಸ್ಯೆಗಳನ್ನು ಹೊಂದಿದೆ."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"ಉದಾಹರಣೆಗೆ, ಲಾಗಿನ್ ಪುಟವು ತೋರಿಸಲಾಗಿರುವ ಸಂಸ್ಥೆಗೆ ಸಂಬಂಧಿಸಿಲ್ಲದಿರಬಹುದು."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"ಪರವಾಗಿಲ್ಲ, ಬ್ರೌಸರ್ ಮೂಲಕ ಮುಂದುವರಿಸಿ"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-ko/strings.xml b/packages/CarrierDefaultApp/res/values-ko/strings.xml
index d6b3d613e3d3..3c6f4a67228f 100644
--- a/packages/CarrierDefaultApp/res/values-ko/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-ko/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"가입하려는 네트워크에 보안 문제가 있습니다."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"예를 들어 로그인 페이지가 표시된 조직에 속하지 않을 수 있습니다."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"브라우저를 통해 계속하기"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-ky/strings.xml b/packages/CarrierDefaultApp/res/values-ky/strings.xml
index 199476f47be0..3cece7de3a18 100644
--- a/packages/CarrierDefaultApp/res/values-ky/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-ky/strings.xml
@@ -1,17 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="app_name" msgid="5247871339820894594">"ОператордунДемейкиКолдонмосу"</string>
+ <string name="app_name" msgid="5247871339820894594">"CarrierDefaultApp"</string>
<string name="android_system_label" msgid="2797790869522345065">"Мобилдик байланыш оператору"</string>
<string name="portal_notification_id" msgid="5155057562457079297">"Мобилдик Интернетиңиздин трафиги түгөндү"</string>
<string name="no_data_notification_id" msgid="668400731803969521">"Мобилдик Интернет өчүрүлгөн"</string>
<string name="portal_notification_detail" msgid="2295729385924660881">"%s сайтына баш багуу үчүн басыңыз"</string>
<string name="no_data_notification_detail" msgid="3112125343857014825">"%s Интернет провайдери менен байланышыңыз"</string>
<string name="no_mobile_data_connection_title" msgid="7449525772416200578">"Мобилдик Интернет жок"</string>
- <string name="no_mobile_data_connection" msgid="544980465184147010">"%s аркылуу дайындарды же роуминг планын кошуу"</string>
+ <string name="no_mobile_data_connection" msgid="544980465184147010">"%s аркылуу маалыматтарды же роуминг планын кошуу"</string>
<string name="mobile_data_status_notification_channel_name" msgid="833999690121305708">"Мобилдик Интернеттин абалы"</string>
<string name="action_bar_label" msgid="4290345990334377177">"Мобилдик тармакка кирүү"</string>
<string name="ssl_error_warning" msgid="3127935140338254180">"Кошулайын деген тармагыңызда коопсуздук көйгөйлөрү бар."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Мисалы, аккаунтка кирүү баракчасы көрсөтүлгөн уюмга таандык эмес болушу мүмкүн."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Баары бир серепчи аркылуу улантуу"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-lo/strings.xml b/packages/CarrierDefaultApp/res/values-lo/strings.xml
index 4a21d7c7493c..c6c0533b09e1 100644
--- a/packages/CarrierDefaultApp/res/values-lo/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-lo/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"ເຄືອຂ່າຍທີ່ທ່ານກຳລັງເຂົ້າຮ່ວມມີບັນຫາຄວາມປອດໄພ."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"ຕົວຢ່າງ, ໜ້າເຂົ້າສູ່ລະບົບອາດຈະບໍ່ແມ່ນຂອງອົງກອນທີ່ປາກົດ."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"ດຳເນີນການຕໍ່ຜ່ານໂປຣແກຣມທ່ອງເວັບ"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-lt/strings.xml b/packages/CarrierDefaultApp/res/values-lt/strings.xml
index be452b79fd71..fe33b1de6c75 100644
--- a/packages/CarrierDefaultApp/res/values-lt/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-lt/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"Kilo tinklo, prie kurio bandote prisijungti, saugos problemų."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Pavyzdžiui, prisijungimo puslapis gali nepriklausyti rodomai organizacijai."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Vis tiek tęsti naudojant naršyklę"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-lv/strings.xml b/packages/CarrierDefaultApp/res/values-lv/strings.xml
index 80a9b5872aad..f8864e2a0e52 100644
--- a/packages/CarrierDefaultApp/res/values-lv/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-lv/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"Tīklā, kuram mēģināt pievienoties, ir drošības problēmas."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Piemēram, pieteikšanās lapa, iespējams, nepieder norādītajai organizācijai."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Tomēr turpināt, izmantojot pārlūkprogrammu"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-mk/strings.xml b/packages/CarrierDefaultApp/res/values-mk/strings.xml
index 96b222c318e2..0b8daafe8d41 100644
--- a/packages/CarrierDefaultApp/res/values-mk/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-mk/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"Мрежата на која се обидувате да се придружите има проблеми со безбедноста."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"На пример, страницата за најавување може да не припаѓа на прикажаната организација."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Сепак продолжи преку прелистувач"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-ml/strings.xml b/packages/CarrierDefaultApp/res/values-ml/strings.xml
index ae08adea09a1..f27d4d864a3b 100644
--- a/packages/CarrierDefaultApp/res/values-ml/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-ml/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"നിങ്ങൾ ചേരാൻ ശ്രമിക്കുന്ന നെറ്റ്‌വർക്കിൽ സുരക്ഷാ പ്രശ്‌നങ്ങളുണ്ടായിരിക്കാം."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"ഉദാഹരണത്തിന്, കാണിച്ചിരിക്കുന്ന ഓർഗനൈസേഷന്റേതായിരിക്കില്ല ലോഗിൻ പേജ്."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"എന്തായാലും ബ്രൗസർ വഴി തുടരുക"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-mn/strings.xml b/packages/CarrierDefaultApp/res/values-mn/strings.xml
index 1a9b72ec3b94..354bd349ac41 100644
--- a/packages/CarrierDefaultApp/res/values-mn/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-mn/strings.xml
@@ -5,7 +5,7 @@
<string name="android_system_label" msgid="2797790869522345065">"Мобайл оператор компани"</string>
<string name="portal_notification_id" msgid="5155057562457079297">"Мобайл дата дууссан"</string>
<string name="no_data_notification_id" msgid="668400731803969521">"Таны мобайл датаг идэвхгүй болгосон"</string>
- <string name="portal_notification_detail" msgid="2295729385924660881">"%s вэб хуудсанд зочлохын тулд товших"</string>
+ <string name="portal_notification_detail" msgid="2295729385924660881">"%s веб хуудсанд зочлохын тулд товших"</string>
<string name="no_data_notification_detail" msgid="3112125343857014825">"%s үйлчилгээ үзүүлэгчтэйгээ холбогдоно уу"</string>
<string name="no_mobile_data_connection_title" msgid="7449525772416200578">"Мобайл дата холболт алга"</string>
<string name="no_mobile_data_connection" msgid="544980465184147010">"Дата эсвэл роуминг төлөвлөгөөг %s-р нэмнэ үү"</string>
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"Таны холбогдох гэж буй сүлжээ аюулгүй байдлын асуудалтай байна."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Жишээлбэл нэвтрэх хуудас нь харагдаж буй байгууллагынх биш байж болно."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Ямар ч тохиолдолд хөтчөөр үргэлжлүүлэх"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-mr/strings.xml b/packages/CarrierDefaultApp/res/values-mr/strings.xml
index 79cc4aa03cfc..c61d3c8e2eea 100644
--- a/packages/CarrierDefaultApp/res/values-mr/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-mr/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"तुम्ही ज्या नेटवर्कमध्‍ये सामील होण्याचा प्रयत्न करत आहात त्यात सुरक्षितता समस्या आहेत."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"उदाहरणार्थ, लॉग इन पृष्‍ठ दर्शवलेल्या संस्थेच्या मालकीचे नसू शकते."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"तरीही ब्राउझरद्वारे सुरू ठेवा"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-ms/strings.xml b/packages/CarrierDefaultApp/res/values-ms/strings.xml
index 7aca5f05e6e7..366463ff90ce 100644
--- a/packages/CarrierDefaultApp/res/values-ms/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-ms/strings.xml
@@ -5,7 +5,7 @@
<string name="android_system_label" msgid="2797790869522345065">"Pembawa Mudah Alih"</string>
<string name="portal_notification_id" msgid="5155057562457079297">"Data mudah alih telah habis"</string>
<string name="no_data_notification_id" msgid="668400731803969521">"Data mudah alih anda telah dinyahaktifkan"</string>
- <string name="portal_notification_detail" msgid="2295729385924660881">"Ketik untuk melawat tapak web %s"</string>
+ <string name="portal_notification_detail" msgid="2295729385924660881">"Ketik untuk melawat laman web %s"</string>
<string name="no_data_notification_detail" msgid="3112125343857014825">"Sila hubungi penyedia perkhidmatan anda, %s"</string>
<string name="no_mobile_data_connection_title" msgid="7449525772416200578">"Tiada sambungan data mudah alih"</string>
<string name="no_mobile_data_connection" msgid="544980465184147010">"Tambahkan data atau pelan perayauan melalui %s"</string>
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"Rangkaian yang cuba anda sertai mempunyai isu keselamatan."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Contohnya, halaman log masuk mungkin bukan milik organisasi yang ditunjukkan."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Teruskan juga melalui penyemak imbas"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-my/strings.xml b/packages/CarrierDefaultApp/res/values-my/strings.xml
index 82372f94e620..2fa618829f51 100644
--- a/packages/CarrierDefaultApp/res/values-my/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-my/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"သင်ချိတ်ဆက်ရန် ကြိုးစားနေသည့် ကွန်ရက်တွင် လုံခြုံရေးပြဿနာများ ရှိနေသည်။"</string>
<string name="ssl_error_example" msgid="6188711843183058764">"ဥပမာ− ဝင်ရောက်ရန် စာမျက်နှာသည် ပြသထားသည့် အဖွဲ့အစည်းနှင့် သက်ဆိုင်မှုမရှိခြင်း ဖြစ်နိုင်ပါသည်။"</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"မည်သို့ပင်ဖြစ်စေ ဘရောက်ဇာမှတစ်ဆင့် ရှေ့ဆက်ရန်"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-nb/strings.xml b/packages/CarrierDefaultApp/res/values-nb/strings.xml
index 1bb9826dc65e..16f8eaadcb0a 100644
--- a/packages/CarrierDefaultApp/res/values-nb/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-nb/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"Nettverket du prøver å logge på, har sikkerhetsproblemer."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Det er for eksempel mulig at påloggingssiden ikke tilhører organisasjonen som vises."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Fortsett likevel via nettleseren"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-ne/strings.xml b/packages/CarrierDefaultApp/res/values-ne/strings.xml
index 2349f9de748c..cb175df24a12 100644
--- a/packages/CarrierDefaultApp/res/values-ne/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-ne/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"तपाईंले सामेल हुने प्रयास गरिरहनु भएको नेटवर्कमा सुरक्षा सम्बन्धी समस्याहरू छन्।"</string>
<string name="ssl_error_example" msgid="6188711843183058764">"उदाहरणका लागि, लग इन पृष्ठ देखाइएको संस्थाको नहुन सक्छ।"</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"जे भए पनि ब्राउजर मार्फत जारी राख्नुहोस्"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-nl/strings.xml b/packages/CarrierDefaultApp/res/values-nl/strings.xml
index 4e2c09bc1e90..8511ff5e9ab3 100644
--- a/packages/CarrierDefaultApp/res/values-nl/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-nl/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"Het netwerk waarmee je verbinding probeert te maken, heeft beveiligingsproblemen."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Zo hoort de weergegeven inlogpagina misschien niet bij de weergegeven organisatie."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Toch doorgaan via browser"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-or/strings.xml b/packages/CarrierDefaultApp/res/values-or/strings.xml
index fd51ed0f78b9..65fd7bb687b5 100644
--- a/packages/CarrierDefaultApp/res/values-or/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-or/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"ଆପଣ ଯୋଗ ଦେବାକୁ ଚେଷ୍ଟା କରୁଥିବା ନେଟୱର୍କର ସୁରକ୍ଷା ସମସ୍ୟା ଅଛି।"</string>
<string name="ssl_error_example" msgid="6188711843183058764">"ଉଦାହରଣସ୍ୱରୂପ, ଲଗଇନ୍‍ ପୃଷ୍ଠା ଦେଖାଯାଇଥିବା ସଂସ୍ଥାର ହୋଇନଥାଇପାରେ।"</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"ବ୍ରାଉଜର୍‍ ଜରିଆରେ ଯେମିତିବି ହେଉ ଜାରି ରଖନ୍ତୁ"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-pa/strings.xml b/packages/CarrierDefaultApp/res/values-pa/strings.xml
index f4d4053bf492..0f096ab1914d 100644
--- a/packages/CarrierDefaultApp/res/values-pa/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-pa/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"ਤੁਸੀਂ ਜਿਸ ਨੈੱਟਵਰਕ ਵਿੱਚ ਸ਼ਾਮਲ ਹੋਣ ਦੀ ਕੋਸ਼ਿਸ਼ ਕਰ ਰਹੇ ਹੋ ਉਸ ਵਿੱਚ ਸੁਰੱਖਿਆ ਸਬੰਧੀ ਸਮੱਸਿਆਵਾਂ ਹਨ।"</string>
<string name="ssl_error_example" msgid="6188711843183058764">"ਉਦਾਹਰਣ ਵੱਜੋਂ, ਲੌਗ-ਇਨ ਪੰਨਾ ਦਿਖਾਈ ਗਈ ਸੰਸਥਾ ਨਾਲ ਸੰਬੰਧਿਤ ਨਹੀਂ ਹੋ ਸਕਦਾ ਹੈ।"</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"ਬ੍ਰਾਊਜ਼ਰ ਰਾਹੀਂ ਫਿਰ ਵੀ ਜਾਰੀ ਰੱਖੋ"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-pl/strings.xml b/packages/CarrierDefaultApp/res/values-pl/strings.xml
index ac45e27a82eb..08bc76731a43 100644
--- a/packages/CarrierDefaultApp/res/values-pl/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-pl/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"W sieci, z którą próbujesz się połączyć, występują problemy z zabezpieczeniami."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Na przykład strona logowania może nie należeć do wyświetlanej organizacji."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Kontynuuj mimo to w przeglądarce"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-pt-rBR/strings.xml b/packages/CarrierDefaultApp/res/values-pt-rBR/strings.xml
index 926de6519b14..23b4152b231d 100644
--- a/packages/CarrierDefaultApp/res/values-pt-rBR/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-pt-rBR/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"A rede à qual você está tentando se conectar tem problemas de segurança."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Por exemplo, a página de login pode não pertencer à organização mostrada."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Continuar mesmo assim pelo navegador"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-pt-rPT/strings.xml b/packages/CarrierDefaultApp/res/values-pt-rPT/strings.xml
index 107a9c28099b..34a564daec0e 100644
--- a/packages/CarrierDefaultApp/res/values-pt-rPT/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-pt-rPT/strings.xml
@@ -14,4 +14,10 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"A rede à qual está a tentar aceder tem problemas de segurança."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Por exemplo, a página de início de sessão pode não pertencer à entidade apresentada."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Continuar mesmo assim através do navegador"</string>
+ <string name="network_boost_notification_channel" msgid="5430986172506159199">"Otimização de rede"</string>
+ <string name="network_boost_notification_title" msgid="8226368121348880044">"%s recomenda um serviço de otimização de dados"</string>
+ <string name="network_boost_notification_detail" msgid="3812434025544196192">"Compre um serviço de otimização de rede para melhorar o desempenho"</string>
+ <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"Agora não"</string>
+ <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"Gerir"</string>
+ <string name="slice_purchase_app_label" msgid="915654761797446390">"Comprar um serviço de otimização de rede."</string>
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-pt/strings.xml b/packages/CarrierDefaultApp/res/values-pt/strings.xml
index 926de6519b14..23b4152b231d 100644
--- a/packages/CarrierDefaultApp/res/values-pt/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-pt/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"A rede à qual você está tentando se conectar tem problemas de segurança."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Por exemplo, a página de login pode não pertencer à organização mostrada."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Continuar mesmo assim pelo navegador"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-ro/strings.xml b/packages/CarrierDefaultApp/res/values-ro/strings.xml
index b91aa813df7f..165952cb7e4c 100644
--- a/packages/CarrierDefaultApp/res/values-ro/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-ro/strings.xml
@@ -5,13 +5,25 @@
<string name="android_system_label" msgid="2797790869522345065">"Operator de telefonie mobilă"</string>
<string name="portal_notification_id" msgid="5155057562457079297">"Datele mobile au expirat"</string>
<string name="no_data_notification_id" msgid="668400731803969521">"Datele mobile au fost dezactivate"</string>
- <string name="portal_notification_detail" msgid="2295729385924660881">"Atingeți pentru a accesa site-ul %s"</string>
- <string name="no_data_notification_detail" msgid="3112125343857014825">"Contactați furnizorul de servicii %s"</string>
+ <string name="portal_notification_detail" msgid="2295729385924660881">"Atinge pentru a accesa site-ul %s"</string>
+ <string name="no_data_notification_detail" msgid="3112125343857014825">"Contactează furnizorul de servicii %s"</string>
<string name="no_mobile_data_connection_title" msgid="7449525772416200578">"Nu există o conexiune de date mobile"</string>
- <string name="no_mobile_data_connection" msgid="544980465184147010">"Adăugați un plan de date sau de roaming prin %s"</string>
+ <string name="no_mobile_data_connection" msgid="544980465184147010">"Adaugă un plan de date sau de roaming prin %s"</string>
<string name="mobile_data_status_notification_channel_name" msgid="833999690121305708">"Starea datelor mobile"</string>
- <string name="action_bar_label" msgid="4290345990334377177">"Conectați-vă la rețeaua mobilă"</string>
+ <string name="action_bar_label" msgid="4290345990334377177">"Conectează-te la rețeaua mobilă"</string>
<string name="ssl_error_warning" msgid="3127935140338254180">"Rețeaua la care încercați să vă conectați are probleme de securitate."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"De exemplu, este posibil ca pagina de conectare să nu aparțină organizației afișate."</string>
- <string name="ssl_error_continue" msgid="1138548463994095584">"Continuați oricum prin browser"</string>
+ <string name="ssl_error_continue" msgid="1138548463994095584">"Continuă oricum prin browser"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-ru/strings.xml b/packages/CarrierDefaultApp/res/values-ru/strings.xml
index ff24f1f822eb..77ce91fecd4b 100644
--- a/packages/CarrierDefaultApp/res/values-ru/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-ru/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"Сеть, к которой вы хотите подключиться, небезопасна."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Например, страница входа в аккаунт может быть фиктивной."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Продолжить в браузере"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-si/strings.xml b/packages/CarrierDefaultApp/res/values-si/strings.xml
index 378a53436a96..fe981ca7cbce 100644
--- a/packages/CarrierDefaultApp/res/values-si/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-si/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"ඔබ සම්බන්ධ වීමට උත්සහ කරන ජාලයේ ආරක්ෂක ගැටළු ඇත."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"උදාහරණයක් ලෙස, පුරනය වන පිටුව පෙන්වා ඇති සංවිධානයට අයිති නැති විය හැක."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"කෙසේ වුවත් බ්‍රවුසරය හරහා ඉදිරියට යන්න"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-sk/strings.xml b/packages/CarrierDefaultApp/res/values-sk/strings.xml
index 9fe38daea3cd..1a2ef1030606 100644
--- a/packages/CarrierDefaultApp/res/values-sk/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-sk/strings.xml
@@ -14,4 +14,10 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"Sieť, ku ktorej sa pokúšate pripojiť, má problémy so zabezpečením"</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Napríklad prihlasovacia stránka nemusí patriť uvedenej organizácii."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Pokračovať pomocou prehliadača"</string>
+ <string name="network_boost_notification_channel" msgid="5430986172506159199">"Zrýchlenie siete"</string>
+ <string name="network_boost_notification_title" msgid="8226368121348880044">"%s odporúča zrýchlenie dátového pripojenia"</string>
+ <string name="network_boost_notification_detail" msgid="3812434025544196192">"Kúpte si zrýchlenie siete zvyšujúce výkon"</string>
+ <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"Teraz nie"</string>
+ <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"Spravovať"</string>
+ <string name="slice_purchase_app_label" msgid="915654761797446390">"Kúpte si zrýchlenie siete."</string>
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-sl/strings.xml b/packages/CarrierDefaultApp/res/values-sl/strings.xml
index bdbc15534adf..1a0f74b931d6 100644
--- a/packages/CarrierDefaultApp/res/values-sl/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-sl/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"Omrežje, ki se mu poskušate pridružiti, ima varnostne težave."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Stran za prijavo na primer morda ne pripada prikazani organizaciji."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Vseeno nadaljuj v brskalniku"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-sq/strings.xml b/packages/CarrierDefaultApp/res/values-sq/strings.xml
index d4899e0db411..f6e19355ac8a 100644
--- a/packages/CarrierDefaultApp/res/values-sq/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-sq/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"Rrjeti në të cilin po përpiqesh të bashkohesh ka probleme sigurie."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"për shembull, faqja e identifikimit mund të mos i përkasë organizatës së shfaqur."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Vazhdo gjithsesi nëpërmjet shfletuesit"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-sr/strings.xml b/packages/CarrierDefaultApp/res/values-sr/strings.xml
index 34c3bdc362ec..e615eadba6da 100644
--- a/packages/CarrierDefaultApp/res/values-sr/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-sr/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"Мрежа којој покушавате да се придружите има безбедносних проблема."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"На пример, страница за пријављивање можда не припада приказаној организацији."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Ипак настави преко прегледача"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-sv/strings.xml b/packages/CarrierDefaultApp/res/values-sv/strings.xml
index 4e76c8d6c746..778663b66d76 100644
--- a/packages/CarrierDefaultApp/res/values-sv/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-sv/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"Nätverket du försöker ansluta till har säkerhetsproblem."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Det kan t.ex. hända att inloggningssidan inte tillhör den organisation som visas."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Fortsätt ändå via webbläsaren"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-sw/strings.xml b/packages/CarrierDefaultApp/res/values-sw/strings.xml
index a52a73335276..4f0745cec6b7 100644
--- a/packages/CarrierDefaultApp/res/values-sw/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-sw/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"Mtandao unaojaribu kujiunga nao una matatizo ya usalama."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Kwa mfano, ukurasa wa kuingia katika akaunti unaweza usiwe unamilikiwa na shirika lililoonyeshwa."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Endelea hata hivyo kupitia kivinjari"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-ta/strings.xml b/packages/CarrierDefaultApp/res/values-ta/strings.xml
index 1a786fa17706..a1d29286d060 100644
--- a/packages/CarrierDefaultApp/res/values-ta/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-ta/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"நீங்கள் சேர முயலும் நெட்வொர்க்கில் பாதுகாப்புச் சிக்கல்கள் உள்ளன."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"எடுத்துக்காட்டாக, உள்நுழைவுப் பக்கமானது காட்டப்படும் அமைப்பிற்குச் சொந்தமானதாக இல்லாமல் இருக்கலாம்."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"பரவாயில்லை, உலாவி வழியாகத் தொடர்க"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-te/strings.xml b/packages/CarrierDefaultApp/res/values-te/strings.xml
index 8877c0cc25dc..71399030c4b1 100644
--- a/packages/CarrierDefaultApp/res/values-te/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-te/strings.xml
@@ -3,15 +3,27 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="5247871339820894594">"CarrierDefaultApp"</string>
<string name="android_system_label" msgid="2797790869522345065">"మొబైల్ క్యారియర్"</string>
- <string name="portal_notification_id" msgid="5155057562457079297">"మొబైల్ డేటాని పూర్తిగా ఉపయోగించారు"</string>
+ <string name="portal_notification_id" msgid="5155057562457079297">"మొబైల్ డేటాను పూర్తిగా ఉపయోగించారు"</string>
<string name="no_data_notification_id" msgid="668400731803969521">"మీ మొబైల్ డేటా నిష్క్రియం చేయబడింది"</string>
<string name="portal_notification_detail" msgid="2295729385924660881">"%s వెబ్‌సైట్‌ని సందర్శించడం కోసం నొక్కండి"</string>
<string name="no_data_notification_detail" msgid="3112125343857014825">"దయచేసి మీ సేవా ప్రదాత %sని సంప్రదించండి"</string>
<string name="no_mobile_data_connection_title" msgid="7449525772416200578">"మొబైల్ డేటా కనెక్షన్ లేదు"</string>
<string name="no_mobile_data_connection" msgid="544980465184147010">"%s ద్వారా డేటాను లేదా రోమింగ్ ప్లాన్‌ను జోడించండి"</string>
<string name="mobile_data_status_notification_channel_name" msgid="833999690121305708">"మొబైల్ డేటా స్థితి"</string>
- <string name="action_bar_label" msgid="4290345990334377177">"మొబైల్ నెట్‌వర్క్‌కి సైన్ ఇన్ చేయి"</string>
+ <string name="action_bar_label" msgid="4290345990334377177">"మొబైల్ నెట్‌వర్క్‌కి సైన్ ఇన్ చేయండి"</string>
<string name="ssl_error_warning" msgid="3127935140338254180">"మీరు చేరడానికి ప్రయత్నిస్తున్న నెట్‌వర్క్ భద్రతా సమస్యలను కలిగి ఉంది."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"ఉదాహరణకు, లాగిన్ పేజీ చూపిన సంస్థకు చెందినది కాకపోవచ్చు."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"ఏదేమైనా బ్రౌజర్ ద్వారా కొనసాగించు"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-th/strings.xml b/packages/CarrierDefaultApp/res/values-th/strings.xml
index 8d30cfdff9ec..5c63bb1bbecd 100644
--- a/packages/CarrierDefaultApp/res/values-th/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-th/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"เครือข่ายที่คุณพยายามเข้าร่วมมีปัญหาด้านความปลอดภัย"</string>
<string name="ssl_error_example" msgid="6188711843183058764">"ตัวอย่างเช่น หน้าเข้าสู่ระบบอาจไม่ใช่ขององค์กรที่แสดงไว้"</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"ดำเนินการต่อผ่านเบราว์เซอร์"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-tl/strings.xml b/packages/CarrierDefaultApp/res/values-tl/strings.xml
index 083ec9ae7c6f..9e320c88e9eb 100644
--- a/packages/CarrierDefaultApp/res/values-tl/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-tl/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"May mga isyu sa seguridad ang network na sinusubukan mong salihan."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Halimbawa, maaaring hindi pag-aari ng ipinapakitang organisasyon ang page ng login."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Magpatuloy pa rin sa pamamagitan ng browser"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-tr/strings.xml b/packages/CarrierDefaultApp/res/values-tr/strings.xml
index aa17431c395a..63616ccab453 100644
--- a/packages/CarrierDefaultApp/res/values-tr/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-tr/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"Katılmaya çalıştığınız ağda güvenlik sorunları var."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Örneğin, giriş sayfası, gösterilen kuruluşa ait olmayabilir."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Yine de tarayıcıyla devam et"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-uk/strings.xml b/packages/CarrierDefaultApp/res/values-uk/strings.xml
index 8381e35b5bed..bd4432711ddd 100644
--- a/packages/CarrierDefaultApp/res/values-uk/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-uk/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"У мережі, до якої ви намагаєтеся під’єднатись, є проблеми з безпекою."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Наприклад, сторінка входу може не належати вказаній організації."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Усе одно продовжити у веб-переглядачі"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-ur/strings.xml b/packages/CarrierDefaultApp/res/values-ur/strings.xml
index fc286b8a53d3..3294cf5723c5 100644
--- a/packages/CarrierDefaultApp/res/values-ur/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-ur/strings.xml
@@ -6,7 +6,7 @@
<string name="portal_notification_id" msgid="5155057562457079297">"موبائل ڈیٹا ختم ہو چکا ہے"</string>
<string name="no_data_notification_id" msgid="668400731803969521">"آپ کا موبائل ڈیٹا غیر فعال کر دیا گیا ہے"</string>
<string name="portal_notification_detail" msgid="2295729385924660881">"‏‎%s ویب سائٹ ملاحظہ کرنے کیلئے تھپتھپائیں"</string>
- <string name="no_data_notification_detail" msgid="3112125343857014825">"‏براہ کرم اپنے خدمت کے فراہم کنندہ %s سے رابطہ کریں"</string>
+ <string name="no_data_notification_detail" msgid="3112125343857014825">"‏براہ کرم اپنے سروس فراہم کنندہ %s سے رابطہ کریں"</string>
<string name="no_mobile_data_connection_title" msgid="7449525772416200578">"کوئی موبائل ڈیٹا کنکشن نہیں ہے"</string>
<string name="no_mobile_data_connection" msgid="544980465184147010">"‏%s کے ذریعے ڈیٹا یا رومنگ پلان شامل کریں"</string>
<string name="mobile_data_status_notification_channel_name" msgid="833999690121305708">"موبائل ڈیٹا کی صورت حال"</string>
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"آپ جس نیٹ ورک میں شامل ہونے کی کوشش کر رہے ہیں، اس میں سیکیورٹی کے مسائل ہیں۔"</string>
<string name="ssl_error_example" msgid="6188711843183058764">"مثال کے طور پر ہو سکتا ہے کہ لاگ ان صفحہ دکھائی گئی تنظیم سے تعلق نہ رکھتا ہو۔"</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"براؤزر کے ذریعے بہرحال جاری رکھیں"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-uz/strings.xml b/packages/CarrierDefaultApp/res/values-uz/strings.xml
index f2801c84a990..4eca545ffd87 100644
--- a/packages/CarrierDefaultApp/res/values-uz/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-uz/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"Siz ulanmoqchi bo‘lgan tarmoqda xavfsizlik bilan bog‘liq muammolar mavjud."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Masalan, tizimga kirish sahifasi ko‘rsatilgan tashkilotga tegishli bo‘lmasligi mumkin."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Brauzerda davom ettirish"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-vi/strings.xml b/packages/CarrierDefaultApp/res/values-vi/strings.xml
index 1047cd4999f1..d8f15e864818 100644
--- a/packages/CarrierDefaultApp/res/values-vi/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-vi/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"Mạng mà bạn đang cố gắng tham gia có vấn đề về bảo mật."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Ví dụ: trang đăng nhập có thể không thuộc về tổ chức được hiển thị."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Vẫn tiếp tục qua trình duyệt"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-zh-rCN/strings.xml b/packages/CarrierDefaultApp/res/values-zh-rCN/strings.xml
index f84cedb5fb7c..4ce19f5d644c 100644
--- a/packages/CarrierDefaultApp/res/values-zh-rCN/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-zh-rCN/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"您尝试加入的网络存在安全问题。"</string>
<string name="ssl_error_example" msgid="6188711843183058764">"例如,登录页面可能并不属于页面上显示的单位。"</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"仍然通过浏览器继续操作"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-zh-rHK/strings.xml b/packages/CarrierDefaultApp/res/values-zh-rHK/strings.xml
index ad76306cb812..f019beb06a6c 100644
--- a/packages/CarrierDefaultApp/res/values-zh-rHK/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-zh-rHK/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"您正在嘗試加入的網絡有安全性問題。"</string>
<string name="ssl_error_example" msgid="6188711843183058764">"例如,登入頁面可能並不屬於所顯示的機構。"</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"仍要透過瀏覽器繼續操作"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-zh-rTW/strings.xml b/packages/CarrierDefaultApp/res/values-zh-rTW/strings.xml
index ccf95c1eebd6..32724b5ae772 100644
--- a/packages/CarrierDefaultApp/res/values-zh-rTW/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-zh-rTW/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"你嘗試加入的網路有安全性問題。"</string>
<string name="ssl_error_example" msgid="6188711843183058764">"例如,登入網頁中顯示的機構可能並非該網頁實際隸屬的機構。"</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"仍要透過瀏覽器繼續操作"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-zu/strings.xml b/packages/CarrierDefaultApp/res/values-zu/strings.xml
index 4ef80c1623ba..669822cbfd18 100644
--- a/packages/CarrierDefaultApp/res/values-zu/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-zu/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"Inethiwekhi ozama ukuyijoyina inezinkinga zokuvikela."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Isibonelo, ikhasi lokungena ngemvume kungenzeka lingelenhlangano ebonisiwe."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Qhubeka noma kunjalo ngesiphequluli"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-af/strings.xml b/packages/CompanionDeviceManager/res/values-af/strings.xml
index 97cae3adc4d5..3d8369a4e9c5 100644
--- a/packages/CompanionDeviceManager/res/values-af/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-af/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"Gee &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; toegang tot jou &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
<string name="profile_name_watch" msgid="576290739483672360">"horlosie"</string>
<string name="chooser_title" msgid="2262294130493605839">"Kies \'n <xliff:g id="PROFILE_NAME">%1$s</xliff:g> om deur &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; bestuur te word"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"Hierdie app is nodig om jou <xliff:g id="DEVICE_NAME">%1$s</xliff:g> te bestuur. <xliff:g id="APP_NAME">%2$s</xliff:g> sal toegelaat word om interaksie met jou kennisgewings te hê, en sal toegang hê tot jou Foon-, SMS-, Kontakte-, Kalender-, Oproeprekords- en Toestelle in die Omtrek-toestemming."</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"Hierdie app is nodig om jou <xliff:g id="DEVICE_NAME">%1$s</xliff:g> te bestuur. <xliff:g id="APP_NAME">%2$s</xliff:g> sal toegelaat word om interaksie met die volgende toestemmings te hê:"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"Gee &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; toegang tot hierdie inligting op jou foon"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"Oorkruistoestel-dienste"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> versoek tans namens jou <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> toestemming om programme tussen jou toestelle te stroom"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Dit kan mikrofoon-, kamera- en liggingtoegang insluit, asook ander sensitiewe toestemmings op &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;Jy kan hierdie toestemmings enige tyd verander in jou Instellings op &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"Program-ikoon"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"Meer Inligting-knoppie"</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"Foon"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"Kontakte"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"Kalender"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"Toestelle in die omtrek"</string>
<string name="permission_storage" msgid="6831099350839392343">"Foto\'s en media"</string>
<string name="permission_notification" msgid="693762568127741203">"Kennisgewings"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"Apps"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"Het toegang tot jou foonnommer en netwerkinligting. Word vereis vir die maak van oproepe en VoIP, stemboodskapdiens, oproepherleiding en die wysiging van oproeprekords"</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"Kan jou kontaklys lees, skep, of wysig, en het toegang tot die lys van al die rekeninge wat op jou toestel gebruik word"</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"Kan alle kennisgewings lees, insluitend inligting soos kontakte, boodskappe en foto\'s"</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"Stroom jou foon se apps"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-am/strings.xml b/packages/CompanionDeviceManager/res/values-am/strings.xml
index 476a25e4cc3b..99d20413f503 100644
--- a/packages/CompanionDeviceManager/res/values-am/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-am/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; የእርስዎን &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; መሣሪያ እንዲደርስ ይፍቀዱለት"</string>
<string name="profile_name_watch" msgid="576290739483672360">"ሰዓት"</string>
<string name="chooser_title" msgid="2262294130493605839">"በ&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; የሚተዳደር <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ይምረጡ"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"የእርስዎን <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ለማስተዳደር መተግበሪያው ያስፈልጋል። <xliff:g id="APP_NAME">%2$s</xliff:g> ከማሳወቂያዎችዎ ጋር መስተጋብር እንዲፈጥር እና የእርስዎን ስልክ፣ ኤስኤምኤስ፣ ዕውቂያዎች፣ የቀን መቁጠሪያ፣ የጥሪ ምዝገባ ማስታወሻዎች እና በአቅራቢያ ያሉ የመሣሪያዎች ፈቃዶች እንዲደርስ ይፈቀድለታል።"</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"የእርስዎን <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ለማስተዳደር መተግበሪያው ያስፈልጋል። <xliff:g id="APP_NAME">%2$s</xliff:g> ከእነዚህ ፈቃዶች ጋር መስተጋብር እንዲፈጥር ይፈቀድለታል፦"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ይህን መረጃ ከስልክዎ እንዲደርስበት ይፍቀዱለት"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"መሣሪያ ተሻጋሪ አገልግሎቶች"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> በእርስዎ መሣሪያዎች መካከል መተግበሪያዎችን በዥረት ለመልቀቅ የእርስዎን <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> ወክሎ ፈቃድ እየጠየቀ ነው"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;ይህ የማይክሮፎን፣ የካሜራ እና የአካባቢ መዳረሻ እና ሌሎች በ&lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt; ላይ ያሉ አደገኛ ፈቃዶችን ሊያካትት ይችላል።እነዚህን ፈቃዶች በማንኛውም ጊዜ በ&lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;ላይ ቅንብሮችዎ ውስጥ መቀየር ይችላሉ።"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"የመተግበሪያ አዶ"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"የተጨማሪ መረጃ አዝራር"</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"ስልክ"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"ኤስኤምኤስ"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"ዕውቂያዎች"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"ቀን መቁጠሪያ"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"በአቅራቢያ ያሉ መሣሪያዎች"</string>
<string name="permission_storage" msgid="6831099350839392343">"ፎቶዎች እና ሚዲያ"</string>
<string name="permission_notification" msgid="693762568127741203">"ማሳወቂያዎች"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"መተግበሪያዎች"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"የእርስዎን ስልክ ቁጥር እና የአውታረ መረብ መረጃ መድረስ ይችላል። ጥሪዎችን ለማድረግ እና VoIP፣ የድምፅ መልዕክት፣ የጥሪ ማዘዋወር እና የጥሪ ምዝገባ ማስታወሻዎችን ለማርትዕ ያስፈልጋል"</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"የእኛን የዕውቂያ ዝርዝር ማንበብ፣ መፍጠር ወይም ማርትዕ እንዲሁም በመሣሪያዎ ላይ ጥቅም ላይ የዋሉትን ሁሉንም መለያዎች ዝርዝር መድረስ ይችላል"</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"እንደ እውቂያዎች፣ መልዕክቶች እና ፎቶዎች ያሉ መረጃዎችን ጨምሮ ሁሉንም ማሳወቂያዎች ማንበብ ይችላል"</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"የስልክዎን መተግበሪያዎች በዥረት ይልቀቁ"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-ar/strings.xml b/packages/CompanionDeviceManager/res/values-ar/strings.xml
index d9ba55583b5d..051a629eebfe 100644
--- a/packages/CompanionDeviceManager/res/values-ar/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ar/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"‏السماح لتطبيق &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; بالوصول إلى &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
<string name="profile_name_watch" msgid="576290739483672360">"الساعة"</string>
<string name="chooser_title" msgid="2262294130493605839">"‏اختَر <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ليديرها تطبيق &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"التطبيق مطلوب لإدارة \"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>\". سيتم السماح لتطبيق \"<xliff:g id="APP_NAME">%2$s</xliff:g>\" بالتفاعل مع الإشعارات والوصول إلى أذونات الهاتف والرسائل القصيرة وجهات الاتصال والتقويم وسجلّات المكالمات والأجهزة المجاورة."</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"التطبيق مطلوب لإدارة \"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>\". سيتم السماح للتطبيق \"<xliff:g id="APP_NAME">%2$s</xliff:g>\" بالتفاعل مع هذه الأذونات."</string>
<string name="title_app_streaming" msgid="2270331024626446950">"‏السماح لتطبيق &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; بالوصول إلى هذه المعلومات من هاتفك"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"الخدمات التي تعمل بين الأجهزة"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"يطلب تطبيق <xliff:g id="APP_NAME">%1$s</xliff:g> الحصول على إذن نيابةً عن <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> لمشاركة التطبيقات بين أجهزتك."</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"‏&lt;p&gt;قد تتضمَّن هذه الأذونات الوصول إلى الميكروفون والكاميرا والموقع الجغرافي وغيرها من أذونات الوصول إلى المعلومات الحسّاسة على &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;يمكنك تغيير هذه الأذونات في أي وقت في إعداداتك على &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"رمز التطبيق"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"زر مزيد من المعلومات"</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"الهاتف"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"الرسائل القصيرة"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"جهات الاتصال"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"التقويم"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"الأجهزة المجاورة"</string>
<string name="permission_storage" msgid="6831099350839392343">"الصور والوسائط"</string>
<string name="permission_notification" msgid="693762568127741203">"الإشعارات"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"التطبيقات"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"يسمح هذا الإذن بالوصول إلى رقم هاتفك ومعلومات الشبكة. ويجب منح هذا الإذن لإجراء مكالمات وتلقّي بريد صوتي عبر بروتوكول الصوت على الإنترنت وإعادة توجيه المكالمات وتعديل سجلات المكالمات."</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"يسمح هذا الإذن بقراءة قائمة جهات الاتصال أو إنشائها أو تعديلها وكذلك قائمة كل الحسابات المُستخدَمة على جهازك."</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"يمكن لهذا الملف الشخصي قراءة جميع الإشعارات، بما في ذلك المعلومات، مثل جهات الاتصال والرسائل والصور."</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"بث تطبيقات هاتفك"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-as/strings.xml b/packages/CompanionDeviceManager/res/values-as/strings.xml
index ef685e6a942f..94c9325a4f8a 100644
--- a/packages/CompanionDeviceManager/res/values-as/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-as/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;ক আপোনাৰ &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; এক্সেছ কৰিবলৈ দিয়ক"</string>
<string name="profile_name_watch" msgid="576290739483672360">"ঘড়ী"</string>
<string name="chooser_title" msgid="2262294130493605839">"&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;এ পৰিচালনা কৰিব লগা এটা <xliff:g id="PROFILE_NAME">%1$s</xliff:g> বাছনি কৰক"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"আপোনাৰ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> পৰিচালনা কৰিবলৈ এপ্‌টোৰ আৱশ্যক। <xliff:g id="APP_NAME">%2$s</xliff:g>ক আপোনাৰ জাননী ব্যৱহাৰ কৰিবলৈ আৰু আপোনাৰ ফ’ন, এছএমএছ, সম্পৰ্ক ,কেলেণ্ডাৰ, কল লগ আৰু নিকটৱৰ্তী ডিভাইচৰ অনুমতি এক্সেছ কৰিবলৈ দিয়া হ’ব।"</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"আপোনাৰ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> পৰিচালনা কৰিবলৈ এপ্‌টোৰ আৱশ্যক। <xliff:g id="APP_NAME">%2$s</xliff:g>ক এই অনুমতিসমূহৰ সৈতে ভাব-বিনিময় কৰিবলৈ দিয়া হ’ব:"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;ক আপোনাৰ ফ’নৰ পৰা এই তথ্যখিনি এক্সেছ কৰাৰ অনুমতি দিয়ক"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"ক্ৰছ-ডিভাইচ সেৱাসমূহ"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g>এ আপোনাৰ <xliff:g id="DEVICE_TYPE">%2$s</xliff:g>ৰ হৈ আপোনাৰ ডিভাইচসমূহৰ মাজত এপ্‌ ষ্ট্ৰীম কৰাৰ বাবে অনুৰোধ জনাইছে"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;ইয়াত &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;ত মাইক্ৰ’ফ’ন, কেমেৰা আৰু অৱস্থানৰ এক্সেছ আৰু অন্য সংবেদশীল অনুমতিসমূহ প্ৰদান কৰাটো অন্তৰ্ভুক্ত হ’ব পাৰে।&lt;/p&gt; &lt;p&gt;আপুনি যিকোনো সময়তে &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;ত থকা আপোনাৰ ছেটিঙত এই অনুমতিসমূহ সলনি কৰিব পাৰে।&lt;/p&gt;"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"এপৰ চিহ্ন"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"অধিক তথ্যৰ বুটাম"</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"ফ’ন"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"এছএমএছ"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"সম্পৰ্ক"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"কেলেণ্ডাৰ"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"নিকটৱৰ্তী ডিভাইচ"</string>
<string name="permission_storage" msgid="6831099350839392343">"ফট’ আৰু মিডিয়া"</string>
<string name="permission_notification" msgid="693762568127741203">"জাননী"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"এপ্"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"কল আৰু VoIP, ভইচমেইল, কল ৰিডাইৰেক্ট আৰু কলৰ লগ সম্পাদনা কৰিবলৈ আৱশ্যক হোৱা আপোনাৰ ফ’ন নম্বৰ আৰু নেটৱৰ্কৰ তথ্য এক্সেছ কৰিব পাৰে"</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"আমাৰ সম্পৰ্কসূচী পঢ়িব, সৃষ্টি কৰিব অথবা সম্পাদনা কৰিব পাৰে আৰু লগতে আপোনাৰ ডিভাইচত ব্যৱহাৰ কৰা আটাইবোৰ একাউণ্টৰ সূচীখন এক্সেছ কৰিব পাৰে"</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"সম্পৰ্কসূচী, বাৰ্তা আৰু ফট’ৰ দৰে তথ্যকে ধৰি আটাইবোৰ জাননী পঢ়িব পাৰে"</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"আপোনাৰ ফ’নৰ এপ্ ষ্ট্ৰীম কৰক"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-az/strings.xml b/packages/CompanionDeviceManager/res/values-az/strings.xml
index b303e0f5e46d..625275dcade8 100644
--- a/packages/CompanionDeviceManager/res/values-az/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-az/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; tətbiqinin &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; cihazınıza girişinə icazə verin"</string>
<string name="profile_name_watch" msgid="576290739483672360">"izləyin"</string>
<string name="chooser_title" msgid="2262294130493605839">"&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; tərəfindən idarə ediləcək <xliff:g id="PROFILE_NAME">%1$s</xliff:g> seçin"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"Tətbiq <xliff:g id="DEVICE_NAME">%1$s</xliff:g> cihazınızı idarə etmək üçün lazımdır. <xliff:g id="APP_NAME">%2$s</xliff:g> bildirişlərinizə, Telefon, SMS, Kontaktlar, Təqvim, Zəng qeydləri və Yaxınlıqdakı cihaz icazələrinə giriş əldə edəcək."</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"Tətbiq <xliff:g id="DEVICE_NAME">%1$s</xliff:g> cihazınızı idarə etmək üçün lazımdır. <xliff:g id="APP_NAME">%2$s</xliff:g> bu icazələrlə qarşılıqlı əlaqəyə icazə veriləcək:"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; tətbiqinə telefonunuzdan bu məlumata giriş icazəsi verin"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"Cihazlararası xidmətlər"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> tətbiqi <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> adından cihazlarınız arasında tətbiqləri yayımlamaq üçün icazə istəyir"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Buraya &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt; cihazındakı Mikrofon, Kamera və Məkana girişi və digər həssas icazələr daxil ola bilər.&lt;/p&gt; &lt;p&gt;Bu icazələri istənilən vaxt &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt; cihazında ayarlarınızda dəyişə bilərsiniz.&lt;/p&gt;"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"Tətbiq İkonası"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"Ətraflı Məlumat Düyməsi"</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"Telefon"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"Kontakt"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"Təqvim"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"Yaxınlıqdakı cihazlar"</string>
<string name="permission_storage" msgid="6831099350839392343">"Foto və media"</string>
<string name="permission_notification" msgid="693762568127741203">"Bildirişlər"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"Tətbiqlər"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"Telefon nömrənizə və şəbəkə məlumatınıza giriş edə bilər. Zəng etmək və VoIP, səsli poçt, zəng yönləndirməsi və zəng qeydlərini redaktə etmək üçün tələb olunur"</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"Kontakt siyahımızı oxuya, yarada və ya redaktə edə, həmçinin cihazınızda istifadə edilən bütün hesabların siyahısına giriş edə bilər"</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"Bütün bildirişləri, o cümlədən kontaktlar, mesajlar və fotolar kimi məlumatları oxuya bilər"</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"Telefonunuzun tətbiqlərini yayımlayın"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-b+sr+Latn/strings.xml b/packages/CompanionDeviceManager/res/values-b+sr+Latn/strings.xml
index a06467257c12..4b12d60c749e 100644
--- a/packages/CompanionDeviceManager/res/values-b+sr+Latn/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-b+sr+Latn/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"Dozvolite da &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; pristupa uređaju &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
<string name="profile_name_watch" msgid="576290739483672360">"sat"</string>
<string name="chooser_title" msgid="2262294130493605839">"Odaberite profil <xliff:g id="PROFILE_NAME">%1$s</xliff:g> kojim će upravljati aplikacija &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"Aplikacija je potrebna za upravljanje uređajem <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> će dobiti dozvolu za interakciju sa obaveštenjima i pristup dozvolama za telefon, SMS, kontakte, kalendar, evidencije poziva i uređaje u blizini."</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"Aplikacija je potrebna za upravljanje uređajem <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> će dobiti dozvolu za interakciju sa ovim dozvolama:"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"Dozvolite da &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; pristupa ovim informacijama sa telefona"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"Usluge na više uređaja"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> zahteva dozvolu u ime uređaja <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> za strimovanje aplikacija između uređaja"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;To može da obuhvata pristup mikrofonu, kameri i lokaciji, kao i drugim osetljivim dozvolama na uređaju &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;U svakom trenutku možete da promenite te dozvole u Podešavanjima na uređaju &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"Ikona aplikacije"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"Dugme za više informacija"</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"Telefon"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"Kontakti"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"Kalendar"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"Uređaji u blizini"</string>
<string name="permission_storage" msgid="6831099350839392343">"Slike i mediji"</string>
<string name="permission_notification" msgid="693762568127741203">"Obaveštenja"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"Aplikacije"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"Može da pristupa vašem broju telefona i informacijama o mreži. Neophodno za upućivanje poziva i VoIP, govornu poštu, preusmeravanje poziva i izmene evidencije poziva"</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"Može da čita, kreira ili menja listu kontakata, kao i da pristupa listi svih naloga koji se koriste na vašem uređaju"</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"Može da čita sva obaveštenja, uključujući informacije poput kontakata, poruka i slika"</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"Strimujte aplikacije na telefonu"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-be/strings.xml b/packages/CompanionDeviceManager/res/values-be/strings.xml
index a42b974beb17..a11f9c1c2afb 100644
--- a/packages/CompanionDeviceManager/res/values-be/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-be/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"Дазвольце праграме &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; доступ да вашай прылады &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
<string name="profile_name_watch" msgid="576290739483672360">"гадзіннік"</string>
<string name="chooser_title" msgid="2262294130493605839">"Выберыце прыладу (<xliff:g id="PROFILE_NAME">%1$s</xliff:g>), якой будзе кіраваць праграма &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"Гэта праграма неабходная для кіравання прыладай \"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>\". <xliff:g id="APP_NAME">%2$s</xliff:g> зможа ўзаемадзейнічаць з вашымі апавяшчэннямі і атрымае доступ да тэлефона, SMS, кантактаў, календара, журналаў выклікаў і прылад паблізу."</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"Гэта праграма неабходная для кіравання прыладай \"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>\". <xliff:g id="APP_NAME">%2$s</xliff:g> зможа выкарыстоўваць наступныя дазволы:"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"Дазвольце праграме &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; мець доступ да гэтай інфармацыі з вашага тэлефона"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"Сэрвісы для некалькіх прылад"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"Праграма \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" запытвае дазвол ад імя вашай прылады \"<xliff:g id="DEVICE_TYPE">%2$s</xliff:g>\" на трансляцыю праграм паміж вашымі прыладамі"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Дазволы могуць уключаць доступ да мікрафона, камеры і даных пра месцазнаходжанне, а таксама да іншай канфідэнцыяльнай інфармацыі на прыладзе &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;Вы можаце ў любы час змяніць гэтыя дазволы ў Наладах на прыладзе &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"Значок праграмы"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"Кнопка \"Даведацца больш\""</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"Тэлефон"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"Кантакты"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"Каляндар"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"Прылады паблізу"</string>
<string name="permission_storage" msgid="6831099350839392343">"Фота і медыяфайлы"</string>
<string name="permission_notification" msgid="693762568127741203">"Апавяшчэнні"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"Праграмы"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"Доступ да вашага нумара тэлефона і інфармацыі пра сетку. Гэты дазвол патрабуецца, каб рабіць звычайныя і VoIP-выклікі, адпраўляць галасавыя паведамленні, перанакіроўваць выклікі і рэдагаваць журналы выклікаў"</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"Магчымасць чытаць, ствараць і рэдагаваць спіс кантактаў, а таксама атрымліваць доступ да спіса ўсіх уліковых запісаў на вашай прыладзе"</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"Можа счытваць усе апавяшчэнні, уключаючы паведамленні, фота і інфармацыю пра кантакты"</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"Трансляцыя змесціва праграм з вашага тэлефона"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-bg/strings.xml b/packages/CompanionDeviceManager/res/values-bg/strings.xml
index a4bd8548c293..e75f39213b5a 100644
--- a/packages/CompanionDeviceManager/res/values-bg/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-bg/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"Разрешаване на &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; да осъществява достъп до устройството ви &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
<string name="profile_name_watch" msgid="576290739483672360">"часовник"</string>
<string name="chooser_title" msgid="2262294130493605839">"Изберете устройство (<xliff:g id="PROFILE_NAME">%1$s</xliff:g>), което да се управлява от &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"Това приложение е необходимо за управление на устройството ви (<xliff:g id="DEVICE_NAME">%1$s</xliff:g>). <xliff:g id="APP_NAME">%2$s</xliff:g> ще получи разрешение да взаимодейства с известията ви и да осъществява достъп до разрешенията за телефона, SMS съобщенията, контактите, календара, списъците с обажданията и разрешенията за устройства в близост."</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"Това приложение е необходимо за управление на устройството ви (<xliff:g id="DEVICE_NAME">%1$s</xliff:g>). <xliff:g id="APP_NAME">%2$s</xliff:g> ще получи разрешение да взаимодейства със следните разрешения:"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"Разрешете на &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; да осъществява достъп до тази информация от телефона ви"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"Услуги за различни устройства"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"„<xliff:g id="APP_NAME">%1$s</xliff:g>“ иска разрешение от името на <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> да предава поточно приложения между устройствата ви"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Това може да включва достъп до микрофона, камерата и местоположението, както и други разрешения за достъп до поверителна информация на &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;Можете да промените тези разрешения по всяко време от настройките на &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"Икона на приложението"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"Бутон за още информация"</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"Телефон"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"Контакти"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"Календар"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"Устройства в близост"</string>
<string name="permission_storage" msgid="6831099350839392343">"Снимки и мултимедия"</string>
<string name="permission_notification" msgid="693762568127741203">"Известия"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"Приложения"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"Може да осъществява достъп до номера и мрежата на телефона ви. Изисква се за провеждането на обаждания и разговори през VoIP, гласовата поща, пренасочването на обаждания и редактирането на списъците с обажданията"</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"Може да чете, създава и редактира записи в списъка с контактите ви, както и да осъществява достъп до списъка с всички профили, използвани на устройството ви"</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"Може да чете всички известия, включително различна информация, като например контакти, съобщения и снимки"</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"Поточно предаване на приложенията на телефона ви"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-bn/strings.xml b/packages/CompanionDeviceManager/res/values-bn/strings.xml
index 2f36c5a7c0c4..8930a81d38c8 100644
--- a/packages/CompanionDeviceManager/res/values-bn/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-bn/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"আপনার &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; অ্যাক্সেস করার জন্য &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;-কে অনুমতি দিন"</string>
<string name="profile_name_watch" msgid="576290739483672360">"ঘড়ি"</string>
<string name="chooser_title" msgid="2262294130493605839">"<xliff:g id="PROFILE_NAME">%1$s</xliff:g> বেছে নিন যেটি &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; ম্যানেজ করবে"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"আপনার <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ম্যানেজ করার জন্য অ্যাপটি প্রয়োজন। <xliff:g id="APP_NAME">%2$s</xliff:g>-কে আপনার বিজ্ঞপ্তির সাথে ইন্টার‌্যাক্ট করার এবং ফোন, এসএমএস, পরিচিতি, ক্যালেন্ডার, কল লগ ও আশেপাশের ডিভাইস অ্যাক্সেস করার অনুমতি দেওয়া হবে।"</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"আপনার <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ম্যানেজ করার জন্য অ্যাপটি প্রয়োজন। <xliff:g id="APP_NAME">%2$s</xliff:g> অ্যাপকে এইসব অনুমতির সাথে ইন্টার‌্যাক্ট করার জন্য অনুমোদন দেওয়া হবে:"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"আপনার ফোন থেকে &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; অ্যাপকে এই তথ্য অ্যাক্সেস করার অনুমতি দিন"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"ক্রস-ডিভাইস পরিষেবা"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"আপনার ডিভাইসগুলির মধ্যে অ্যাপ স্ট্রিম করার জন্য <xliff:g id="APP_NAME">%1$s</xliff:g>, <xliff:g id="DEVICE_TYPE">%2$s</xliff:g>-এর হয়ে অনুমতি চাইছে"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;এটি &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&amp;gt-এ হয়ত মাইক্রোফোন, ক্যামেরা এবং লোকেশনের অ্যাক্সেস ও অন্যান্য সংবেদনশীল অনুমতি অন্তর্ভুক্ত করতে পারে;আপনি যেকোনও সময় &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;-এর \'সেটিংস\'-এ গিয়ে এইসব অনুমতি পরিবর্তন করতে পারবেন"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"অ্যাপের আইকন"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"আরও তথ্য সংক্রান্ত বোতাম"</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"ফোন"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"এসএমএস"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"পরিচিতি"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"ক্যালেন্ডার"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"আশেপাশের ডিভাইস"</string>
<string name="permission_storage" msgid="6831099350839392343">"ফটো ও মিডিয়া"</string>
<string name="permission_notification" msgid="693762568127741203">"বিজ্ঞপ্তি"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"অ্যাপ"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"আপনার ফোন নম্বর ও নেটওয়ার্ক সংক্রান্ত তথ্য অ্যাক্সেস করতে পারবে। কল করার জন্য এবং VoIP, ভয়েসমেল, কল রিডাইরেক্ট ও কল লগ এডিট করার জন্য যা প্রয়োজন"</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"আমাদের পরিচিতি তালিকা দেখতে, তৈরি বা এডিট করতে পারবে, পাশাপাশি আপনার ডিভাইসে ব্যবহার করা হয় এমন সবকটি অ্যাকাউন্টের তালিকা অ্যাক্সেস করতে পারবে"</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"সব বিজ্ঞপ্তি পড়তে পারবে, যার মধ্যে পরিচিতি, মেসেজ ও ফটোর মতো তথ্য অন্তর্ভুক্ত"</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"আপনার ফোনের অ্যাপ স্ট্রিম করুন"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-bs/strings.xml b/packages/CompanionDeviceManager/res/values-bs/strings.xml
index 78f6e2ce99ef..2a455331564d 100644
--- a/packages/CompanionDeviceManager/res/values-bs/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-bs/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"Dozvolite aplikaciji &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; da pristupa uređaju &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
<string name="profile_name_watch" msgid="576290739483672360">"sat"</string>
<string name="chooser_title" msgid="2262294130493605839">"Odaberite uređaj <xliff:g id="PROFILE_NAME">%1$s</xliff:g> kojim će upravljati aplikacija &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"Aplikacija je potrebna za upravljanje uređajem <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. Aplikaciji <xliff:g id="APP_NAME">%2$s</xliff:g> će se dozvoliti da ostvaruje interakciju s vašim obavještenjima i da pristupa odobrenjima za telefon, SMS-ove, kontakte, kalendar, zapisnike poziva i uređaje u blizini."</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"Aplikacija je potrebna za upravljanje uređajem <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. Aplikaciji <xliff:g id="APP_NAME">%2$s</xliff:g> će biti dozvoljena interakcija s ovim odobrenjima:"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"Dozvolite da aplikacija &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; pristupa ovim informacijama s telefona"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"Usluga na više uređaja"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> u ime uređaja <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> zahtijeva odobrenje da prenosi aplikacije između vaših uređaja"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Ovo može uključivati pristup mikrofonu, kameri i lokaciji i druga osjetljiva odobrenja na uređaju &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;Uvijek možete promijeniti ova odobrenja u Postavkama na uređaju &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"Ikona aplikacije"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"Dugme Više informacija"</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"Telefon"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"Kontakti"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"Kalendar"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"Uređaji u blizini"</string>
<string name="permission_storage" msgid="6831099350839392343">"Fotografije i mediji"</string>
<string name="permission_notification" msgid="693762568127741203">"Obavještenja"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"Aplikacije"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"Može pristupiti vašem broju telefona i informacijama o mreži. Potrebno je za upućivanje poziva i VoIP, govornu poštu, preusmjeravanje poziva te uređivanje zapisnika poziva"</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"Može čitati, kreirati ili uređivati našu listu kontakata te pristupiti listi svih računa koji se koriste na vašem uređaju"</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"Može čitati sva obavještenja, uključujući informacije kao što su kontakti, poruke i fotografije"</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"Prenosite aplikacije s telefona"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-ca/strings.xml b/packages/CompanionDeviceManager/res/values-ca/strings.xml
index 840c89c65f5c..8995cdd63113 100644
--- a/packages/CompanionDeviceManager/res/values-ca/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ca/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"Permet que &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; accedeixi a &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
<string name="profile_name_watch" msgid="576290739483672360">"rellotge"</string>
<string name="chooser_title" msgid="2262294130493605839">"Tria un <xliff:g id="PROFILE_NAME">%1$s</xliff:g> perquè el gestioni &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"L\'aplicació és necessària per gestionar <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> tindrà permís per interaccionar amb les teves notificacions i accedir al telèfon, als SMS, als contactes, al calendari, als registres de trucades i als dispositius propers."</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"L\'aplicació és necessària per gestionar <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> tindrà permís per interaccionar amb aquests permisos:"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"Permet que &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; accedeixi a aquesta informació del telèfon"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"Serveis multidispositiu"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> demana permís en nom del teu <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> per reproduir en continu aplicacions entre els dispositius"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Això pot incloure accés al micròfon, a la càmera i a la ubicació, i altres permisos sensibles a &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;Pots canviar aquests permisos en qualsevol moment a &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;, a Configuració.&lt;/p&gt;"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"Icona de l\'aplicació"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"Botó Més informació"</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"Telèfon"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"Contactes"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"Calendari"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"Dispositius propers"</string>
<string name="permission_storage" msgid="6831099350839392343">"Fotos i contingut multimèdia"</string>
<string name="permission_notification" msgid="693762568127741203">"Notificacions"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"Aplicacions"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"Pot accedir al teu número de telèfon i a la informació de la xarxa. Es requereix per fer trucades i VoIP, enviar missatges de veu, redirigir trucades i editar els registres de trucades."</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"Pot llegir, crear o editar la nostra llista de contactes i també accedir a la llista de tots els comptes que s\'utilitzen al teu dispositiu"</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"Pot llegir totes les notificacions, inclosa informació com ara els contactes, els missatges i les fotos"</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"Reprodueix en continu aplicacions del telèfon"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-cs/strings.xml b/packages/CompanionDeviceManager/res/values-cs/strings.xml
index 0ce29cf45703..fdf93d8dbd01 100644
--- a/packages/CompanionDeviceManager/res/values-cs/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-cs/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"Povolit aplikaci &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; přístup k vašemu zařízení &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
<string name="profile_name_watch" msgid="576290739483672360">"hodinky"</string>
<string name="chooser_title" msgid="2262294130493605839">"Vyberte zařízení <xliff:g id="PROFILE_NAME">%1$s</xliff:g>, které chcete spravovat pomocí aplikace &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"Aplikace je nutná ke správě zařízení <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. Aplikace <xliff:g id="APP_NAME">%2$s</xliff:g> bude moci interagovat s vašimi oznámeními a získá přístup k telefonu, SMS, kontaktům, kalendáři, seznamům hovorů a zařízením v okolí."</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"Aplikace je nutná ke správě zařízení <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. Aplikace <xliff:g id="APP_NAME">%2$s</xliff:g> bude moci interagovat s těmito oprávněními:"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"Povolte aplikaci &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; přístup k těmto informacím z vašeho telefonu"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"Služby pro více zařízení"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"Aplikace <xliff:g id="APP_NAME">%1$s</xliff:g> požaduje za vaše zařízení <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> oprávnění ke streamování aplikací mezi zařízeními"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Může být zahrnut přístup k mikrofonu, fotoaparátu a poloze a další citlivá oprávnění na zařízení &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;Tato oprávnění můžete v Nastavení na zařízení &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt; kdykoliv změnit.&lt;/p&gt;"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"Ikona aplikace"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"Tlačítko Další informace"</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"Telefon"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"Kontakty"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"Kalendář"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"Zařízení v okolí"</string>
<string name="permission_storage" msgid="6831099350839392343">"Fotky a média"</string>
<string name="permission_notification" msgid="693762568127741203">"Oznámení"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"Aplikace"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"Má přístup k vašemu telefonnímu číslu a informacím o síti. Vyžadováno pro volání a VoIP, hlasové zprávy, přesměrování hovorů a úpravy seznamů hovorů."</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"Může načítat, vytvářet a upravovat váš seznam kontaktů a má přístup k seznamu všech účtů používaných v zařízení"</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"Může číst veškerá oznámení včetně informací, jako jsou kontakty, zprávy a fotky"</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"Streamujte aplikace v telefonu"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-da/strings.xml b/packages/CompanionDeviceManager/res/values-da/strings.xml
index 32f958febe28..51fcc627a1d7 100644
--- a/packages/CompanionDeviceManager/res/values-da/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-da/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"Tillad, at &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; får adgang til dit &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
<string name="profile_name_watch" msgid="576290739483672360">"ur"</string>
<string name="chooser_title" msgid="2262294130493605839">"Vælg den enhed (<xliff:g id="PROFILE_NAME">%1$s</xliff:g>), som skal administreres af &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"Du skal bruge denne app for at administrere <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> får tilladelse til at interagere med dine notifikationer og får adgang til dine tilladelser for Opkald, Sms, Kalender, Opkaldshistorik og Enheder i nærheden."</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"Du skal bruge denne app for at administrere <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> får mulighed for at interagere med følgende tilladelser:"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"Giv &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; adgang til disse oplysninger fra din telefon"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"Tjenester, som kan tilsluttes en anden enhed"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> anmoder om tilladelse på vegne af din <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> til at streame apps mellem dine enheder"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Dette kan omfatte mikrofon-, kamera- og lokationsadgang samt andre tilladelser til at tilgå følsomme oplysninger på &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;Du kan til enhver tid ændre disse tilladelser under Indstillinger på &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"Appikon"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"Knappen Flere oplysninger"</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"Telefon"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"Sms"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"Kontakter"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"Kalender"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"Enheder i nærheden"</string>
<string name="permission_storage" msgid="6831099350839392343">"Billeder og medier"</string>
<string name="permission_notification" msgid="693762568127741203">"Notifikationer"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"Apps"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"Har adgang til oplysninger om dit telefonnummer og netværk. Påkrævet for at foretage opkald og VoIP, talebeskeder, omdirigering og redigering af opkaldshistorikken"</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"Kan læse, oprette eller redigere din liste over kontakter samt tilgå lister for alle de konti, der bruges på din enhed"</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"Kan læse alle notifikationer, herunder oplysninger som f.eks. kontakter, beskeder og billeder"</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"Stream din telefons apps"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-de/strings.xml b/packages/CompanionDeviceManager/res/values-de/strings.xml
index 42c6fdec170b..8747256eff0b 100644
--- a/packages/CompanionDeviceManager/res/values-de/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-de/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; erlauben, auf dein Gerät (&lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;) zuzugreifen"</string>
<string name="profile_name_watch" msgid="576290739483672360">"Smartwatch"</string>
<string name="chooser_title" msgid="2262294130493605839">"Gerät „<xliff:g id="PROFILE_NAME">%1$s</xliff:g>“ auswählen, das von &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; verwaltet werden soll"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"Die App wird zur Verwaltung deines Geräts (<xliff:g id="DEVICE_NAME">%1$s</xliff:g>) benötigt. <xliff:g id="APP_NAME">%2$s</xliff:g> darf mit deinen Benachrichtigungen interagieren und auf die Berechtigungen „Telefon“, „SMS“, „Kontakte“, „Kalender“, „Anrufliste“ und „Geräte in der Nähe“ zugreifen."</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"Die App wird zur Verwaltung deines Geräts (<xliff:g id="DEVICE_NAME">%1$s</xliff:g>) benötigt. <xliff:g id="APP_NAME">%2$s</xliff:g> darf mit diesen Berechtigungen interagieren:"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; Zugriff auf diese Informationen von deinem Smartphone gewähren"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"Geräteübergreifende Dienste"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> bittet für dein <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> um die Berechtigung zum Streamen von Apps zwischen deinen Geräten"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Dazu können Berechtigungen für Mikrofon, Kamera und Standortzugriff sowie andere vertrauliche Berechtigungen auf &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt; gehören.&lt;/p&gt;&lt;p&gt;Sie lassen sich jederzeit in den Einstellungen auf &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt; ändern.&lt;/p&gt;"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"App-Symbol"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"Weitere-Infos-Schaltfläche"</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"Telefon"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"Kontakte"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"Kalender"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"Geräte in der Nähe"</string>
<string name="permission_storage" msgid="6831099350839392343">"Fotos und Medien"</string>
<string name="permission_notification" msgid="693762568127741203">"Benachrichtigungen"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"Apps"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"Darf auf deine Telefonnummer und Netzwerkinformationen zugreifen. Erforderlich für normale und VoIP-Anrufe, Mailbox, Anrufweiterleitung und das Bearbeiten von Anruflisten"</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"Darf deine Kontaktliste lesen, erstellen oder bearbeiten sowie auf die Kontaktliste aller auf diesem Gerät verwendeten Konten zugreifen"</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"Kann alle Benachrichtigungen lesen, einschließlich Informationen wie Kontakten, Nachrichten und Fotos"</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"Smartphone-Apps streamen"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-el/strings.xml b/packages/CompanionDeviceManager/res/values-el/strings.xml
index 28ab7a31d19b..ccd56117aa4f 100644
--- a/packages/CompanionDeviceManager/res/values-el/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-el/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"Επιτρέψτε στην εφαρμογή &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; να έχει πρόσβαση στη συσκευή &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
<string name="profile_name_watch" msgid="576290739483672360">"ρολόι"</string>
<string name="chooser_title" msgid="2262294130493605839">"Επιλέξτε ένα προφίλ <xliff:g id="PROFILE_NAME">%1$s</xliff:g> για διαχείριση από την εφαρμογή &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"Η εφαρμογή είναι απαραίτητη για τη διαχείριση της συσκευής <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. Η εφαρμογή <xliff:g id="APP_NAME">%2$s</xliff:g> θα επιτρέπεται να αλληλεπιδρά με τις ειδοποιήσεις και να έχει πρόσβαση στις άδειες Τηλέφωνο, SMS, Επαφές, Ημερολόγιο, Αρχεία καταγραφής κλήσεων και Συσκευές σε κοντινή απόσταση."</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"Η εφαρμογή είναι απαραίτητη για τη διαχείριση της συσκευής <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. Η εφαρμογή <xliff:g id="APP_NAME">%2$s</xliff:g> θα επιτρέπεται να αλληλεπιδρά με τις εξής άδειες:"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"Να επιτρέπεται στο &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; η πρόσβαση σε αυτές τις πληροφορίες από το τηλέφωνό σας."</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"Υπηρεσίες πολλών συσκευών"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"Η εφαρμογή <xliff:g id="APP_NAME">%1$s</xliff:g> ζητά εκ μέρους της συσκευής σας <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> άδεια για ροή εφαρμογών μεταξύ των συσκευών σας"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Αυτές μπορεί να περιλαμβάνουν πρόσβαση σε μικρόφωνο, κάμερα και τοποθεσία και άλλες άδειες πρόσβασης σε ευαίσθητες πληροφορίες στη συσκευή &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;Μπορείτε να αλλάξετε αυτές τις άδειες ανά πάσα στιγμή στις Ρυθμίσεις σας στη συσκευή &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"Εικονίδιο εφαρμογής"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"Κουμπί περισσότερων πληροφορ."</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"Τηλέφωνο"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"Επαφές"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"Ημερολόγιο"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"Συσκευές σε κοντινή απόσταση"</string>
<string name="permission_storage" msgid="6831099350839392343">"Φωτογραφίες και μέσα"</string>
<string name="permission_notification" msgid="693762568127741203">"Ειδοποιήσεις"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"Εφαρμογές"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"Δυνατότητα πρόσβασης στον αριθμό τηλεφώνου σας και στις πληροφορίες δικτύου. Απαιτείται για την πραγματοποίηση κλήσεων και για υπηρεσίες VoIP, μηνύματα αυτόματου τηλεφωνητή, ανακατεύθυνση κλήσεων και επεξεργασία αρχείων καταγραφής κλήσεων"</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"Δυνατότητα ανάγνωσης, δημιουργίας ή επεξεργασίας της λίστας επαφών σας, καθώς και πρόσβασης στη λίστα επαφών όλων των λογαριασμών που χρησιμοποιούνται στη συσκευή σας"</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"Μπορεί να διαβάσει όλες τις ειδοποιήσεις, συμπεριλαμβανομένων πληροφοριών όπως επαφές, μηνύματα και φωτογραφίες"</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"Μεταδώστε σε ροή τις εφαρμογές του τηλεφώνου σας"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-en-rAU/strings.xml b/packages/CompanionDeviceManager/res/values-en-rAU/strings.xml
index 89aebbd208cf..1d7623fe18f6 100644
--- a/packages/CompanionDeviceManager/res/values-en-rAU/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-en-rAU/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"Allow &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; to access your &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
<string name="profile_name_watch" msgid="576290739483672360">"watch"</string>
<string name="chooser_title" msgid="2262294130493605839">"Choose a <xliff:g id="PROFILE_NAME">%1$s</xliff:g> to be managed by &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"The app is needed to manage your <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> will be allowed to interact with your notifications and access your phone, SMS, contacts, calendar, call logs and Nearby devices permissions."</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"The app is needed to manage your <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> will be allowed to interact with these permissions:"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"Allow &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; to access this information from your phone"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"Cross-device services"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> to stream apps between your devices"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;This may include microphone, camera and location access, and other sensitive permissions on &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;You can change these permissions at any time in your settings on &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"App icon"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"More information button"</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"Phone"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"Contacts"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"Calendar"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"Nearby devices"</string>
<string name="permission_storage" msgid="6831099350839392343">"Photos and media"</string>
<string name="permission_notification" msgid="693762568127741203">"Notifications"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"Apps"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"Can access your phone number and network info. Required for making calls and VoIP, voicemail, call redirect and editing call logs"</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"Can read, create or edit our contact list, as well as access the list of all accounts used on your device"</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"Can read all notifications, including information like contacts, messages and photos"</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"Stream your phone’s apps"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-en-rCA/strings.xml b/packages/CompanionDeviceManager/res/values-en-rCA/strings.xml
index d6f95ad9cf08..61ae5371e0c6 100644
--- a/packages/CompanionDeviceManager/res/values-en-rCA/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-en-rCA/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"Allow &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; to access your &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
<string name="profile_name_watch" msgid="576290739483672360">"watch"</string>
<string name="chooser_title" msgid="2262294130493605839">"Choose a <xliff:g id="PROFILE_NAME">%1$s</xliff:g> to be managed by &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"The app is needed to manage your <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> will be allowed to interact with your notifications and access your Phone, SMS, Contacts, Calendar, Call logs and Nearby devices permissions."</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"The app is needed to manage your <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> will be allowed to interact with these permissions:"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"Allow &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; to access this information from your phone"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"Cross-device services"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> to stream apps between your devices"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;This may include Microphone, Camera, and Location access, and other sensitive permissions on &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;You can change these permissions any time in your Settings on &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"App Icon"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"More Information Button"</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"Phone"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"Contacts"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"Calendar"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"Nearby devices"</string>
<string name="permission_storage" msgid="6831099350839392343">"Photos and media"</string>
<string name="permission_notification" msgid="693762568127741203">"Notifications"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"Apps"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"Can access your phone number and network info. Required for making calls and VoIP, voicemail, call redirect, and editing call logs"</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"Can read, create, or edit our contact list, as well as access the list of all accounts used on your device"</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"Can read all notifications, including information like contacts, messages, and photos"</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"Stream your phone’s apps"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-en-rGB/strings.xml b/packages/CompanionDeviceManager/res/values-en-rGB/strings.xml
index 89aebbd208cf..1d7623fe18f6 100644
--- a/packages/CompanionDeviceManager/res/values-en-rGB/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-en-rGB/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"Allow &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; to access your &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
<string name="profile_name_watch" msgid="576290739483672360">"watch"</string>
<string name="chooser_title" msgid="2262294130493605839">"Choose a <xliff:g id="PROFILE_NAME">%1$s</xliff:g> to be managed by &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"The app is needed to manage your <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> will be allowed to interact with your notifications and access your phone, SMS, contacts, calendar, call logs and Nearby devices permissions."</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"The app is needed to manage your <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> will be allowed to interact with these permissions:"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"Allow &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; to access this information from your phone"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"Cross-device services"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> to stream apps between your devices"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;This may include microphone, camera and location access, and other sensitive permissions on &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;You can change these permissions at any time in your settings on &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"App icon"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"More information button"</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"Phone"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"Contacts"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"Calendar"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"Nearby devices"</string>
<string name="permission_storage" msgid="6831099350839392343">"Photos and media"</string>
<string name="permission_notification" msgid="693762568127741203">"Notifications"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"Apps"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"Can access your phone number and network info. Required for making calls and VoIP, voicemail, call redirect and editing call logs"</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"Can read, create or edit our contact list, as well as access the list of all accounts used on your device"</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"Can read all notifications, including information like contacts, messages and photos"</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"Stream your phone’s apps"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-en-rIN/strings.xml b/packages/CompanionDeviceManager/res/values-en-rIN/strings.xml
index 89aebbd208cf..1d7623fe18f6 100644
--- a/packages/CompanionDeviceManager/res/values-en-rIN/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-en-rIN/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"Allow &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; to access your &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
<string name="profile_name_watch" msgid="576290739483672360">"watch"</string>
<string name="chooser_title" msgid="2262294130493605839">"Choose a <xliff:g id="PROFILE_NAME">%1$s</xliff:g> to be managed by &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"The app is needed to manage your <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> will be allowed to interact with your notifications and access your phone, SMS, contacts, calendar, call logs and Nearby devices permissions."</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"The app is needed to manage your <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> will be allowed to interact with these permissions:"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"Allow &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; to access this information from your phone"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"Cross-device services"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> to stream apps between your devices"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;This may include microphone, camera and location access, and other sensitive permissions on &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;You can change these permissions at any time in your settings on &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"App icon"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"More information button"</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"Phone"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"Contacts"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"Calendar"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"Nearby devices"</string>
<string name="permission_storage" msgid="6831099350839392343">"Photos and media"</string>
<string name="permission_notification" msgid="693762568127741203">"Notifications"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"Apps"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"Can access your phone number and network info. Required for making calls and VoIP, voicemail, call redirect and editing call logs"</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"Can read, create or edit our contact list, as well as access the list of all accounts used on your device"</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"Can read all notifications, including information like contacts, messages and photos"</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"Stream your phone’s apps"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-en-rXC/strings.xml b/packages/CompanionDeviceManager/res/values-en-rXC/strings.xml
index 54c4931237a4..d40ac6a3ee52 100644
--- a/packages/CompanionDeviceManager/res/values-en-rXC/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-en-rXC/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‎‎‏‎‎‎‎‏‏‏‎‎‎‎‎‏‎‏‏‎‏‏‎‏‏‏‎‏‏‏‏‏‏‎‎‏‎‎‎‎‏‏‎‏‎‏‏‏‎‎‎‎‏‎‎‏‎‏‎Allow &lt;strong&gt;‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎&lt;/strong&gt; to access your &lt;strong&gt;‎‏‎‎‏‏‎<xliff:g id="DEVICE_NAME">%2$s</xliff:g>‎‏‎‎‏‏‏‎&lt;/strong&gt;‎‏‎‎‏‎"</string>
<string name="profile_name_watch" msgid="576290739483672360">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‎‎‏‎‏‎‏‎‏‏‏‏‏‏‏‎‎‏‏‏‎‎‏‎‎‎‏‎‏‎‏‏‏‏‏‏‏‎‎‏‎‏‎‎‎‎watch‎‏‎‎‏‎"</string>
<string name="chooser_title" msgid="2262294130493605839">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‏‏‎‏‏‎‎‏‎‏‎‏‎‎‏‎‎‎‏‎‎‎‎‏‏‎‏‎‎‎‏‎‎‏‏‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‏‏‎‎‏‏‏‏‎Choose a ‎‏‎‎‏‏‎<xliff:g id="PROFILE_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ to be managed by &lt;strong&gt;‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%2$s</xliff:g>‎‏‎‎‏‏‏‎&lt;/strong&gt;‎‏‎‎‏‎"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‎‎‏‎‏‏‎‎‏‏‏‎‏‎‏‎‎‎‏‎‏‏‎‏‏‎‏‏‎‏‎‎‏‏‏‎‎‏‎‎‎‏‎‏‎‏‏‎‎‎‎‏‏‎‎‏‏‎‎The app is needed to manage your ‎‏‎‎‏‏‎<xliff:g id="DEVICE_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎. ‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%2$s</xliff:g>‎‏‎‎‏‏‏‎ will be allowed to interact with your notifications and access your Phone, SMS, Contacts, Calendar, Call logs and Nearby devices permissions.‎‏‎‎‏‎"</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‎‏‎‎‏‎‎‎‏‏‎‎‎‏‏‎‏‏‏‎‏‏‎‎‎‏‏‏‏‏‏‏‎‎‏‎‏‎‏‎‎‏‎‏‎‏‎‏‎‏‎‎‎‏‎‏‎‎‎The app is needed to manage your ‎‏‎‎‏‏‎<xliff:g id="DEVICE_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎. ‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%2$s</xliff:g>‎‏‎‎‏‏‏‎ will be allowed to interact with these permissions:‎‏‎‎‏‎"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‏‏‏‎‎‎‎‎‎‏‏‏‎‏‎‏‏‎‎‎‎‎‏‎‎‏‏‏‎‎‎‏‎‏‎‏‏‎‏‎‎‎‎‏‏‏‎‎‏‎‎‏‏‎‎‏‏‎‎Allow &lt;strong&gt;‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎&lt;/strong&gt; to access this information from your phone‎‏‎‎‏‎"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‎‏‏‎‎‏‏‏‎‏‏‏‎‎‎‎‎‏‎‏‎‏‎‏‎‏‎‏‎‎‎‎‏‎‏‎‏‎‎‏‎‏‎‎‏‏‎‏‎‏‏‏‏‎‎‏‎‏‎Cross-device services‎‏‎‎‏‎"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‎‏‎‏‏‎‏‎‏‎‎‏‏‏‏‎‎‏‎‎‎‎‎‏‏‎‏‏‎‎‎‏‎‎‏‏‎‎‎‎‏‏‏‏‎‏‎‎‏‏‎‏‏‎‎‏‏‎‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ is requesting permission on behalf of your ‎‏‎‎‏‏‎<xliff:g id="DEVICE_TYPE">%2$s</xliff:g>‎‏‎‎‏‏‏‎ to stream apps between your devices‎‏‎‎‏‎"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‏‏‏‎‎‎‏‎‏‎‎‏‏‏‏‎‏‏‏‎‎‎‏‏‎‎‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‎‎‎‏‎‎‏‏‎‏‏‏‏‏‏‎‎‎&lt;p&gt;This may include Microphone, Camera, and Location access, and other sensitive permissions on &lt;strong&gt;‎‏‎‎‏‏‎<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>‎‏‎‎‏‏‏‎&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;You can change these permissions any time in your Settings on &lt;strong&gt;‎‏‎‎‏‏‎<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>‎‏‎‎‏‏‏‎&lt;/strong&gt;.&lt;/p&gt;‎‏‎‎‏‎"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‎‏‏‎‏‏‎‎‏‎‏‏‏‎‏‏‎‎‎‎‎‎‎‏‎‏‎‏‏‎‎‎‏‎‎‎‎‎‏‏‏‎‏‎‏‏‏‏‏‎‏‎‏‎‏‏‎‏‎App Icon‎‏‎‎‏‎"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‏‏‎‎‏‎‎‎‎‏‎‏‏‏‎‏‏‎‏‏‎‏‎‏‎‏‎‎‏‎‎‏‎‎‏‏‎‏‎‎‎‎‎‎‏‏‎‏‏‎‎‏‎‏‎‎‎‎‎More Information Button‎‏‎‎‏‎"</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‎‎‏‏‏‎‏‏‏‎‎‎‎‎‏‏‏‏‎‎‏‎‎‏‎‎‎‏‎‏‏‏‎‏‏‏‏‏‏‏‏‏‎‎‏‎‏‎‏‏‎‎‎‏‎‏‏‏‎Phone‎‏‎‎‏‎"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‏‏‏‏‏‏‎‎‏‎‎‎‎‎‏‏‎‎‎‏‏‎‏‏‏‎‏‏‏‏‏‏‏‏‎‏‏‏‏‎‎‏‎‎‏‎‎‏‏‎‎‏‎‎‎‎‏‎‎SMS‎‏‎‎‏‎"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‎‏‏‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‎‎‎‎‏‎‎‎‎‏‎‎‎‎‏‎‏‏‎‎‏‏‎‏‎‎‎‏‏‎‏‎‏‎‏‏‎‎Contacts‎‏‎‎‏‎"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‏‎‎‏‏‏‎‎‏‎‏‎‎‏‎‏‏‏‎‏‎‏‎‏‏‏‎‏‏‎‏‏‏‏‎‎‎‎‏‏‎‎‏‎‏‎‏‎‎‏‎‎‎‏‏‎‏‏‎Calendar‎‏‎‎‏‎"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‎‎‏‎‎‎‎‎‏‏‎‏‏‎‎‏‎‎‎‏‎‏‎‎‎‎‎‏‎‎‎‎‎‎‏‎‎‏‎‎‏‎‏‏‎‎‎‏‏‎‏‎‎‏‏‎‎‏‎Nearby devices‎‏‎‎‏‎"</string>
<string name="permission_storage" msgid="6831099350839392343">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‏‎‏‏‎‎‏‏‎‎‏‏‏‏‎‎‎‎‏‎‏‎‏‎‏‎‏‎‏‏‏‎‎‎‎‎‏‎‏‏‏‎‏‏‏‏‏‎‎‎‎‏‎‏‎‏‏‏‎Photos and media‎‏‎‎‏‎"</string>
<string name="permission_notification" msgid="693762568127741203">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‏‏‎‏‎‎‎‎‎‏‎‏‏‏‏‎‏‎‏‏‎‎‎‎‏‎‏‎‎‎‏‏‏‏‎‏‏‎‏‏‏‎‏‏‏‏‏‎‏‎‎‎‏‎‎‏‏‎Notifications‎‏‎‎‏‎"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‏‏‎‏‏‎‎‏‏‎‏‎‏‏‏‎‎‏‏‏‏‏‎‏‏‎‎‎‎‎‎‏‏‎‎‏‎‏‎‎‎‎‎‏‎‏‎‎‏‏‎‎‎‏‎‏‏‎‎Apps‎‏‎‎‏‎"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‎‏‎‏‏‎‏‎‎‎‎‎‎‏‏‎‏‎‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‏‎‎‏‏‏‏‏‏‏‏‏‏‎‎‎‏‏‏‏‏‎‏‎‏‎Can access your phone number and network info. Required for making calls and VoIP, voicemail, call redirect, and editing call logs‎‏‎‎‏‎"</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‎‎‏‏‏‏‎‏‎‎‎‎‎‎‎‎‎‏‏‎‎‎‏‎‏‎‏‏‎‎‏‎‎‏‎‎‏‎‎‎‎‎‎‏‎‏‎‎‏‏‏‏‎‎‏‎‎‎‎Can read, create, or edit our contact list, as well as access the list of all accounts used on your device‎‏‎‎‏‎"</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‎‎‎‏‎‎‎‏‎‎‏‏‎‏‏‏‎‏‏‏‎‏‎‎‎‎‏‎‎‎‎‏‎‏‎‏‎‎‎‎‎‏‏‎‏‏‎‎‎‏‏‏‏‎‎‎‏‏‎Can read all notifications, including information like contacts, messages, and photos‎‏‎‎‏‎"</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‎‎‏‏‎‏‏‎‎‎‎‏‏‏‎‎‏‏‎‎‎‏‏‎‎‏‎‎‏‎‎‎‏‎‎‎‎‏‎‎‏‎‎‎‏‎‏‎‏‎‏‏‎‎‎‎‎‎Stream your phone’s apps‎‏‎‎‏‎"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-es-rUS/strings.xml b/packages/CompanionDeviceManager/res/values-es-rUS/strings.xml
index 31e9b6650166..ef7e59d74c74 100644
--- a/packages/CompanionDeviceManager/res/values-es-rUS/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-es-rUS/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"Permite que &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; acceda a tu &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
<string name="profile_name_watch" msgid="576290739483672360">"reloj"</string>
<string name="chooser_title" msgid="2262294130493605839">"Elige un <xliff:g id="PROFILE_NAME">%1$s</xliff:g> para que la app &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; lo administre"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"Esta app es necesaria para administrar tu <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> podrá interactuar con tus notificaciones y acceder a los permisos de Teléfono, SMS, Contactos, Calendario, Llamadas y Dispositivos cercanos."</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"Esta app es necesaria para administrar tu <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> podrá interactuar con estos permisos:"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"Permite que &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; acceda a esta información de tu teléfono"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"Servicios multidispositivo"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> solicita tu permiso en nombre de <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> para transmitir apps entre dispositivos"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Esto puede incluir el acceso al micrófono, la cámara y la ubicación, así como otros permisos sensibles del dispositivo &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;Puedes cambiar estos permisos en cualquier momento en la Configuración del dispositivo &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"Ícono de la app"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"Botón Más información"</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"Teléfono"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"Contactos"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"Calendario"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"Dispositivos cercanos"</string>
<string name="permission_storage" msgid="6831099350839392343">"Fotos y contenido multimedia"</string>
<string name="permission_notification" msgid="693762568127741203">"Notificaciones"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"Apps"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"Puede acceder a tu número de teléfono y a la información de la red (es obligatorio para realizar llamadas VoIP, enviar mensajes de voz, redireccionar llamadas y editar registros de llamadas)"</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"Puede leer, crear o editar la lista de contactos, además de acceder a la lista de contactos para todas las cuentas que se usan en tu dispositivo"</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"Puede leer todas las notificaciones, incluso con información como contactos, mensajes y fotos"</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"Transmitir las apps de tu teléfono"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-es/strings.xml b/packages/CompanionDeviceManager/res/values-es/strings.xml
index bcfff13272ae..82ff28ab6985 100644
--- a/packages/CompanionDeviceManager/res/values-es/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-es/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"Permitir que &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; acceda a tu &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
<string name="profile_name_watch" msgid="576290739483672360">"reloj"</string>
<string name="chooser_title" msgid="2262294130493605839">"Elige un <xliff:g id="PROFILE_NAME">%1$s</xliff:g> para gestionarlo con &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"Se necesita la aplicación para gestionar tu <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> podrá interactuar con tus notificaciones y acceder a tus permisos de teléfono, SMS, contactos, calendario, registros de llamadas y dispositivos cercanos."</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"Se necesita la aplicación para gestionar tu <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. Se permitirá que <xliff:g id="APP_NAME">%2$s</xliff:g> interaccione con los siguientes permisos:"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"Permitir que &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; acceda a esta información de tu teléfono"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"Servicios multidispositivo"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> está pidiendo permiso en nombre de tu <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> para emitir aplicaciones en otros dispositivos tuyos"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Esto puede incluir acceso al micrófono, la cámara y la ubicación, así como otros permisos sensibles de &lt;p&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;Puedes cambiar estos permisos cuando quieras en los ajustes de &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;."</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"Icono de la aplicación"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"Botón Más información"</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"Teléfono"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"Contactos"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"Calendario"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"Dispositivos cercanos"</string>
<string name="permission_storage" msgid="6831099350839392343">"Fotos y elementos multimedia"</string>
<string name="permission_notification" msgid="693762568127741203">"Notificaciones"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"Aplicaciones"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"Puede acceder a tu número de teléfono e información de red. Es necesario para hacer llamadas y VoIP, enviar mensajes de voz, redirigir llamadas y editar registros de llamadas"</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"Puede leer, crear o editar tu lista de contactos, así como acceder a la lista de contactos de todas las cuentas que se usan en tu dispositivo"</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"Puede leer todas las notificaciones, incluida información como contactos, mensajes y fotos"</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"Muestra en streaming las aplicaciones de tu teléfono"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-et/strings.xml b/packages/CompanionDeviceManager/res/values-et/strings.xml
index b267ce544c8f..ab42dda9f012 100644
--- a/packages/CompanionDeviceManager/res/values-et/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-et/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"Lubage rakendusel &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; teie seadmele &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; juurde pääseda"</string>
<string name="profile_name_watch" msgid="576290739483672360">"käekell"</string>
<string name="chooser_title" msgid="2262294130493605839">"Valige seade <xliff:g id="PROFILE_NAME">%1$s</xliff:g>, mida haldab rakendus &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"Seda rakendust on vaja teie seadme <xliff:g id="DEVICE_NAME">%1$s</xliff:g> haldamiseks. Rakendusel <xliff:g id="APP_NAME">%2$s</xliff:g> lubatakse kasutada teie märguandeid ning pääseda juurde teie telefoni, SMS-ide, kontaktide, kalendri, kõnelogide ja läheduses olevate seadmete lubadele."</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"Seda rakendust on vaja teie seadme <xliff:g id="DEVICE_NAME">%1$s</xliff:g> haldamiseks. Rakendusel <xliff:g id="APP_NAME">%2$s</xliff:g> lubatakse kasutada järgmisi lube."</string>
<string name="title_app_streaming" msgid="2270331024626446950">"Lubage rakendusel &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; pääseda teie telefonis juurde sellele teabele"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"Seadmeülesed teenused"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> taotleb teie seadme <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> nimel luba teie seadmete vahel rakendusi voogesitada"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;See võib hõlmata mikrofoni, kaamerat ja juurdepääsu asukohale ning muid tundlikke lube seadmes &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;Võite neid lube seadme &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt; seadetes igal ajal muuta.&lt;/p&gt;"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"Rakenduse ikoon"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"Nupp Lisateave"</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"Telefon"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"Kontaktid"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"Kalender"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"Läheduses olevad seadmed"</string>
<string name="permission_storage" msgid="6831099350839392343">"Fotod ja meedia"</string>
<string name="permission_notification" msgid="693762568127741203">"Märguanded"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"Rakendused"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"Pääseb juurde teie telefoninumbrile ja võrguteabele. Nõutav helistamiseks, VoIP-i ja kõneposti kasutamiseks, kõnede ümbersuunamiseks ning kõnelogide muutmiseks."</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"Saab lugeda, luua või muuta kontaktiloendit ja pääseda juurde kõigi teie seadmes kasutatavate kontode loendile"</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"Kõikide märguannete, sealhulgas teabe, nagu kontaktid, sõnumid ja fotod, lugemine"</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"Telefoni rakenduste voogesitamine"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-eu/strings.xml b/packages/CompanionDeviceManager/res/values-eu/strings.xml
index 3140762f181d..66d433d14b0e 100644
--- a/packages/CompanionDeviceManager/res/values-eu/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-eu/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"Eman &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; atzitzeko baimena &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; aplikazioari"</string>
<string name="profile_name_watch" msgid="576290739483672360">"erlojua"</string>
<string name="chooser_title" msgid="2262294130493605839">"Aukeratu &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; aplikazioak kudeatu beharreko <xliff:g id="PROFILE_NAME">%1$s</xliff:g>"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> kudeatzeko behar da aplikazioa. Jakinarazpenekin interakzioan aritzeko, eta telefonoa, SMSak, kontaktuak, egutegia, deien erregistroak eta inguruko gailuak erabiltzeko baimenak izango ditu <xliff:g id="APP_NAME">%2$s</xliff:g> aplikazioak."</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> kudeatzeko behar da aplikazioa. Baimen hauek izango ditu <xliff:g id="APP_NAME">%2$s</xliff:g> aplikazioak:"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"Eman informazioa telefonotik hartzeko baimena &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; aplikazioari"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"Gailu baterako baino gehiagotarako zerbitzuak"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"Gailu batetik bestera aplikazioak igortzeko baimena eskatzen ari da <xliff:g id="APP_NAME">%1$s</xliff:g>, <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> gailuaren izenean"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Haien artean, baliteke &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt; gailuaren mikrofonoa, kamera, kokapenerako sarbidea eta beste kontuzko baimen batzuk egotea.&lt;/p&gt; &lt;p&gt;Baimen horiek edonoiz alda ditzakezu &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt; gailuaren ezarpenetan.&lt;/p&gt;"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"Aplikazioaren ikonoa"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"Informazio gehiagorako botoia"</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"Telefonoa"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"SMSak"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"Kontaktuak"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"Egutegia"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"Inguruko gailuak"</string>
<string name="permission_storage" msgid="6831099350839392343">"Argazkiak eta multimedia-edukia"</string>
<string name="permission_notification" msgid="693762568127741203">"Jakinarazpenak"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"Aplikazioak"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"Telefono-zenbakia eta sareari buruzko informazioa atzi ditzake. Dei arruntak eta VoIP bidezko deiak egiteko, erantzungailurako, deiak birbideratzeko aukerarako eta deien erregistroan editatzeko behar da."</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"Kontaktuen zerrenda irakurri, sortu edo edita dezake, baita kontuan erabilitako kontu guztien zerrenda atzitu ere"</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"Jakinarazpen guztiak irakur ditzake; besteak beste, kontaktuak, mezuak, argazkiak eta antzeko informazioa"</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"Igorri zuzenean telefonoko aplikazioak"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-fa/strings.xml b/packages/CompanionDeviceManager/res/values-fa/strings.xml
index b6421b3646e7..b00cb5b6bb6f 100644
--- a/packages/CompanionDeviceManager/res/values-fa/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-fa/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"‏به &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; اجازه دهید به &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; دسترسی داشته باشد"</string>
<string name="profile_name_watch" msgid="576290739483672360">"ساعت"</string>
<string name="chooser_title" msgid="2262294130493605839">"‏انتخاب <xliff:g id="PROFILE_NAME">%1$s</xliff:g> برای مدیریت کردن با &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>‏&lt;/strong&gt;"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"این برنامه برای مدیریت <xliff:g id="DEVICE_NAME">%1$s</xliff:g> شما لازم است. <xliff:g id="APP_NAME">%2$s</xliff:g> می‌تواند با اعلان‌های شما تعامل داشته باشد و به اجازه‌های «تلفن»، «پیامک»، «مخاطبین»، «تقویم»، «گزارش‌های تماس» و «دستگاه‌های اطراف» دسترسی خواهد داشت."</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"این برنامه برای مدیریت <xliff:g id="DEVICE_NAME">%1$s</xliff:g> شما لازم است. <xliff:g id="APP_NAME">%2$s</xliff:g> مجاز است با این اجازه‌ها تعامل داشته باشد:"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"‏اجازه دادن به &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; برای دسترسی به اطلاعات تلفن"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"سرویس‌های بین‌دستگاهی"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> اجازه می‌خواهد ازطرف <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> برنامه‌ها را بین دستگاه‌های شما جاری‌سازی کند"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"‏&lt;p&gt;این اجازه‌ها می‌تواند شامل دسترسی به «میکروفون»، «دوربین»، و «مکان»، و دیگر اجازه‌های حساس در &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt; شود.&lt;/p&gt; &lt;p&gt;هروقت بخواهید می‌توانید این اجازه‌ها را در «تنظیمات» در &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt; تغییر دهید.&lt;/p&gt;"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"نماد برنامه"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"دکمه اطلاعات بیشتر"</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"تلفن"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"پیامک"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"مخاطبین"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"تقویم"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"دستگاه‌های اطراف"</string>
<string name="permission_storage" msgid="6831099350839392343">"عکس‌ها و رسانه‌ها"</string>
<string name="permission_notification" msgid="693762568127741203">"اعلان‌ها"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"برنامه‌ها"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"‏می‌تواند به شماره تلفن و اطلاعات شبکه‌تان دسترسی داشته باشد. برای برقراری تماس‌های تلفنی و VoIP، استفاده از پست صوتی، هدایت تماس، و ویرایش گزارش‌های تماس لازم است"</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"می‌تواند فهرست مخاطبین ما را بخواند و ایجاد یا ویرایش کند و همچنین می‌تواند به فهرست همه حساب‌های مورداستفاده در دستگاهتان دسترسی داشته باشد"</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"می‌تواند همه اعلان‌ها، ازجمله اطلاعاتی مثل مخاطبین، پیام‌ها، و عکس‌ها را بخواند"</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"جاری‌سازی برنامه‌های تلفن"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-fi/strings.xml b/packages/CompanionDeviceManager/res/values-fi/strings.xml
index d71ad89da69b..d4136c74ee5a 100644
--- a/packages/CompanionDeviceManager/res/values-fi/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-fi/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"Salli, että &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; saa pääsyn laitteeseesi: &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
<string name="profile_name_watch" msgid="576290739483672360">"kello"</string>
<string name="chooser_title" msgid="2262294130493605839">"Valitse <xliff:g id="PROFILE_NAME">%1$s</xliff:g>, jota &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; hallinnoi"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"Laitteen (<xliff:g id="DEVICE_NAME">%1$s</xliff:g>) ylläpitoon tarvitaan tätä sovellusta. <xliff:g id="APP_NAME">%2$s</xliff:g> saa luvan hallinnoida ilmoituksiasi sekä pääsyn puhelimeen, tekstiviesteihin, yhteystietoihin, kalenteriin, puhelulokeihin ja lähellä olevat laitteet ‑lupiin."</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"Laitteen (<xliff:g id="DEVICE_NAME">%1$s</xliff:g>) ylläpitoon tarvitaan tätä sovellusta. <xliff:g id="APP_NAME">%2$s</xliff:g> saa käyttää näitä lupia:"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"Salli, että &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; saa pääsyn näihin puhelimesi tietoihin"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"Laitteidenväliset palvelut"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> pyytää laitteesi (<xliff:g id="DEVICE_TYPE">%2$s</xliff:g>) puolesta lupaa striimata sovelluksia laitteidesi välillä"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Tähän voi kuulua pääsy mikrofoniin, kameraan ja sijaintiin sekä muita arkaluontoisia lupia laitteella &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;Voit muuttaa lupia asetuksista milloin tahansa laitteella &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"Sovelluskuvake"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"Lisätietopainike"</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"Puhelin"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"Tekstiviesti"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"Yhteystiedot"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"Kalenteri"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"Lähellä olevat laitteet"</string>
<string name="permission_storage" msgid="6831099350839392343">"Kuvat ja media"</string>
<string name="permission_notification" msgid="693762568127741203">"Ilmoitukset"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"Sovellukset"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"Voi nähdä puhelinnumerosi ja verkon tiedot. Tätä tarvitaan puheluiden soittamiseen, VoIP:n, puhelinvastaajan ja puheluiden uudelleenohjauksen käyttämiseen sekä puhelulokien muokkaamiseen."</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"Voi luoda yhteystietolistan tai lukea tai muokata sitä sekä avata listan kaikilla tileillä, joita käytetään laitteellasi."</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"Voi lukea kaikkia ilmoituksia, esim. kontakteihin, viesteihin ja kuviin liittyviä tietoja"</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"Striimaa puhelimen sovelluksia"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-fr-rCA/strings.xml b/packages/CompanionDeviceManager/res/values-fr-rCA/strings.xml
index 95f512a97969..7c170390e164 100644
--- a/packages/CompanionDeviceManager/res/values-fr-rCA/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-fr-rCA/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"Autoriser &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; à accéder à votre &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
<string name="profile_name_watch" msgid="576290739483672360">"montre"</string>
<string name="chooser_title" msgid="2262294130493605839">"Choisissez un(e) <xliff:g id="PROFILE_NAME">%1$s</xliff:g> qui sera géré(e) par &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"L\'application est nécessaire pour gérer votre <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> aura l\'autorisation d\'interagir avec vos notifications et d\'accéder aux autorisations suivantes : téléphone, messages texte, contacts, agenda, journaux d\'appels et appareils à proximité."</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"L\'application est nécessaire pour gérer votre <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> aura l\'autorisation d\'interagir avec ces autorisations :"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"Autorisez &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; à accéder à ces informations à partir de votre téléphone"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"Services multiappareils"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> demande l\'autorisation au nom de votre <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> pour diffuser des applications entre vos appareils"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Cela peut comprendre l\'accès au microphone, à l\'appareil photo et à la position, ainsi que d\'autres autorisations sensibles sur &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;Vous pouvez modifier ces autorisations en tout temps dans vos paramètres sur &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"Icône de l\'application"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"Bouton En savoir plus"</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"Téléphone"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"Messages texte"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"Contacts"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"Agenda"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"Appareils à proximité"</string>
<string name="permission_storage" msgid="6831099350839392343">"Photos et fichiers multimédias"</string>
<string name="permission_notification" msgid="693762568127741203">"Notifications"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"Applications"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"Peut accéder à votre numéro de téléphone et à vos renseignements de réseau. Ceci est nécessaire pour passer des appels téléphoniques et des appels voix sur IP, laisser un message vocal, rediriger les appels et modifier les journaux d\'appels"</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"Peut lire, créer ou modifier notre liste de contacts et accéder à la liste de tous les comptes utilisés sur votre appareil"</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"Peut lire toutes les notifications, y compris les renseignements tels que les contacts, les messages et les photos"</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"Diffusez les applications de votre téléphone"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-fr/strings.xml b/packages/CompanionDeviceManager/res/values-fr/strings.xml
index aa4da5b018d9..fde2322a84c3 100644
--- a/packages/CompanionDeviceManager/res/values-fr/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-fr/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"Autoriser &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; à accéder à votre &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
<string name="profile_name_watch" msgid="576290739483672360">"montre"</string>
<string name="chooser_title" msgid="2262294130493605839">"Sélectionnez le/la <xliff:g id="PROFILE_NAME">%1$s</xliff:g> qui sera géré(e) par &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"Cette appli est nécessaire pour gérer votre <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> aura l\'autorisation d\'interagir avec vos notifications et d\'accéder au téléphone, aux SMS, aux contacts, à l\'agenda, aux journaux d\'appels et aux appareils à proximité."</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"Cette appli est nécessaire pour gérer votre <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> pourra interagir avec ces autorisations :"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"Autoriser &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; à accéder à ces informations depuis votre téléphone"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"Services inter-appareils"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> demande l\'autorisation au nom de votre <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> pour caster des applis d\'un appareil à l\'autre"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Il peut s\'agir de l\'accès au micro, à l\'appareil photo et à la position, et d\'autres autorisations sensibles sur l\'appareil &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;Vous pouvez modifier ces autorisations à tout moment dans les paramètres de l\'appareil &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"Icône d\'application"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"Bouton Plus d\'informations"</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"Téléphone"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"Contacts"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"Agenda"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"Appareils à proximité"</string>
<string name="permission_storage" msgid="6831099350839392343">"Photos et contenus multimédias"</string>
<string name="permission_notification" msgid="693762568127741203">"Notifications"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"Applis"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"Peut accéder à votre numéro de téléphone et aux informations réseau. Nécessaire pour passer des appels et pour VoIP, la messagerie vocale, la redirection d\'appels et la modification des journaux d\'appels."</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"Peut lire, créer ou modifier votre liste de contacts, et accéder à la liste de tous les comptes utilisés sur votre appareil"</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"Peut lire toutes les notifications, y compris des informations comme les contacts, messages et photos"</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"Diffuser en streaming les applis de votre téléphone"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-gl/strings.xml b/packages/CompanionDeviceManager/res/values-gl/strings.xml
index 2a7af1877845..214c3f5d1606 100644
--- a/packages/CompanionDeviceManager/res/values-gl/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-gl/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"Permitir que &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; acceda ao teu dispositivo (&lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;)"</string>
<string name="profile_name_watch" msgid="576290739483672360">"reloxo"</string>
<string name="chooser_title" msgid="2262294130493605839">"Escolle un perfil (<xliff:g id="PROFILE_NAME">%1$s</xliff:g>) para que o xestione a aplicación &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"A aplicación é necesaria para xestionar o teu dispositivo (<xliff:g id="DEVICE_NAME">%1$s</xliff:g>). <xliff:g id="APP_NAME">%2$s</xliff:g> poderá interactuar coas túas notificacións e acceder aos permisos do teu teléfono, das SMS, dos contactos, do calendario, dos rexistros de chamadas e dos dispositivos próximos."</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"A aplicación é necesaria para xestionar o teu dispositivo (<xliff:g id="DEVICE_NAME">%1$s</xliff:g>). <xliff:g id="APP_NAME">%2$s</xliff:g> poderá interactuar con estes permisos:"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"Permitir que a aplicación &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; acceda a esta información desde o teu teléfono"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"Servizos multidispositivo"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> está solicitando permiso en nome do teu dispositivo (<xliff:g id="DEVICE_TYPE">%2$s</xliff:g>) para emitir contido de aplicacións entre os teus aparellos"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Con esta acción podes conceder acceso ao micrófono, á cámara e á localización, así como outros permisos de acceso á información confidencial de &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;Podes cambiar estes permisos en calquera momento na configuración de &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"Icona de aplicación"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"Botón de máis información"</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"Teléfono"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"Contactos"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"Calendario"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"Dispositivos próximos"</string>
<string name="permission_storage" msgid="6831099350839392343">"Fotos e contido multimedia"</string>
<string name="permission_notification" msgid="693762568127741203">"Notificacións"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"Aplicacións"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"Pode acceder ao teu número de teléfono e á información de rede do dispositivo. Necesítase para facer chamadas, usar VoIP, acceder ao correo de voz, redirixir chamadas e modificar os rexistros de chamadas"</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"Pode ler, crear ou editar a túa lista de contactos, así como acceder á lista de todas as contas usadas no teu dispositivo"</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"Pode ler todas as notificacións (que poden incluír información como contactos, mensaxes e fotos)"</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"Emite as aplicacións do teu teléfono"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-gu/strings.xml b/packages/CompanionDeviceManager/res/values-gu/strings.xml
index 6483d1b3ff12..6f5ebea1a37c 100644
--- a/packages/CompanionDeviceManager/res/values-gu/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-gu/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"તમારા &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;ને ઍક્સેસ કરવાની &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;ને મંજૂરી આપો"</string>
<string name="profile_name_watch" msgid="576290739483672360">"સ્માર્ટવૉચ"</string>
<string name="chooser_title" msgid="2262294130493605839">"&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; દ્વારા મેનેજ કરવા માટે કોઈ <xliff:g id="PROFILE_NAME">%1$s</xliff:g> પસંદ કરો"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"તમારી <xliff:g id="DEVICE_NAME">%1$s</xliff:g> મેનેજ કરવા માટે ઍપ જરૂરી છે. <xliff:g id="APP_NAME">%2$s</xliff:g>ને તમારા નોટિફિકેશન સાથે ક્રિયાપ્રતિક્રિયા કરવાની તેમજ તમારો ફોન, SMS, સંપર્કો, કૅલેન્ડર, કૉલ લૉગ અને નજીકના ડિવાઇસની પરવાનગીઓ ઍક્સેસ કરવાની મંજૂરી આપવામાં આવશે."</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"તમારી <xliff:g id="DEVICE_NAME">%1$s</xliff:g> મેનેજ કરવા માટે ઍપ જરૂરી છે. <xliff:g id="APP_NAME">%2$s</xliff:g>ને આ પરવાનગીઓ સાથે ક્રિયાપ્રતિક્રિયા કરવાની મંજૂરી આપવામાં આવશે:"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"તમારા ફોનમાંથી આ માહિતી ઍક્સેસ કરવા માટે, &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;ને મંજૂરી આપો"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"ક્રોસ-ડિવાઇસ સેવાઓ"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> તમારા <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> વતી તમારા ડિવાઇસ વચ્ચે ઍપ સ્ટ્રીમ કરવાની પરવાનગીની વિનંતી કરી રહી છે"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;આમાં &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt; પરના માઇક્રોફોન, કૅમેરા અને સ્થાનના ઍક્સેસ તથા અન્ય સંવેદનશીલ માહિતીની પરવાનગીઓ શામેલ હોઈ શકે છે.&lt;/p&gt; &lt;p&gt;તમે &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt; પર તમારા સેટિંગમાં તમે કોઈપણ સમયે આ પરવાનગીઓને બદલી શકો છો.&lt;/p&gt;"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"ઍપનું આઇકન"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"વધુ માહિતી માટેનું બટન"</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"ફોન"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"સંપર્કો"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"કૅલેન્ડર"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"નજીકના ડિવાઇસ"</string>
<string name="permission_storage" msgid="6831099350839392343">"ફોટા અને મીડિયા"</string>
<string name="permission_notification" msgid="693762568127741203">"નોટિફિકેશન"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"ઍપ"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"તમારો ફોન નંબર અને નેટવર્કની માહિતી ઍક્સેસ કરી શકે છે. કૉલ અને VoIP કૉલ, વૉઇસમેઇલ કરવા, કૉલ રીડાયરેક્ટ કરવા તથા કૉલ લૉગમાં ફેરફાર કરવા માટે જરૂરી છે"</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"તમારી સંપર્ક સૂચિ વાંચી, બનાવી શકે છે અથવા તેમાં ફેરફાર કરી શકે છે તેમજ તમારા ડિવાઇસ પર ઉપયોગમાં લેવાતા બધા એકાઉન્ટની સંપર્ક સૂચિને ઍક્સેસ કરી શકે છે"</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"સંપર્કો, મેસેજ અને ફોટા જેવી માહિતી સહિતના બધા નોટિફિકેશન વાંચી શકે છે"</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"તમારા ફોનની ઍપ સ્ટ્રીમ કરો"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-hr/strings.xml b/packages/CompanionDeviceManager/res/values-hr/strings.xml
index a9895ebc2250..437ab990c645 100644
--- a/packages/CompanionDeviceManager/res/values-hr/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-hr/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"Dopustite aplikaciji &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; da pristupa vašem uređaju &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
<string name="profile_name_watch" msgid="576290739483672360">"satom"</string>
<string name="chooser_title" msgid="2262294130493605839">"Odaberite profil <xliff:g id="PROFILE_NAME">%1$s</xliff:g> kojim će upravljati aplikacija &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"Aplikacija je potrebna za upravljanje vašim uređajem <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. Aplikacija <xliff:g id="APP_NAME">%2$s</xliff:g> moći će stupati u interakciju s vašim obavijestima i pristupati dopuštenjima za telefon, SMS-ove, kontakte, kalendar, zapisnike poziva i uređaje u blizini."</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"Aplikacija je potrebna za upravljanje vašim uređajem <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. Aplikacija <xliff:g id="APP_NAME">%2$s</xliff:g> moći će stupati u interakciju s ovim dopuštenjima:"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"Omogućite aplikaciji &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; da pristupa informacijama s vašeg telefona"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"Usluge na različitim uređajima"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> zahtijeva dopuštenje u ime vašeg uređaja <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> za emitiranje aplikacija između vaših uređaja"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;To može uključivati pristup mikrofonu, kameri i lokaciji i druga dopuštenja za osjetljive podatke na uređaju &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;Ta dopuštenja uvijek možete promijeniti u postavkama na uređaju &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"Ikona aplikacije"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"Gumb Više informacija"</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"Telefon"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"Kontakti"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"Kalendar"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"Uređaji u blizini"</string>
<string name="permission_storage" msgid="6831099350839392343">"Fotografije i mediji"</string>
<string name="permission_notification" msgid="693762568127741203">"Obavijesti"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"Aplikacije"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"Može pristupati vašem broju telefona i podacima o mreži. Potrebno je za uspostavu poziva i VoIP, govornu poštu, preusmjeravanje poziva i uređivanje zapisnika poziva"</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"Može čitati, izrađivati ili uređivati vaš popis kontakata te pristupati popisu kontakata svih računa korištenih na vašem uređaju"</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"Može čitati sve obavijesti, uključujući informacije kao što su kontakti, poruke i fotografije"</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"Streaming aplikacija vašeg telefona"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-hu/strings.xml b/packages/CompanionDeviceManager/res/values-hu/strings.xml
index 83f681aa8fd9..de7aac13bc33 100644
--- a/packages/CompanionDeviceManager/res/values-hu/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-hu/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"A(z) &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; hozzáférésének engedélyezése a(z) &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; eszközhöz"</string>
<string name="profile_name_watch" msgid="576290739483672360">"óra"</string>
<string name="chooser_title" msgid="2262294130493605839">"A(z) &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; alkalmazással kezelni kívánt <xliff:g id="PROFILE_NAME">%1$s</xliff:g> kiválasztása"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"Szükség van az alkalmazásra a következő kezeléséhez: <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. A(z) <xliff:g id="APP_NAME">%2$s</xliff:g> műveleteket végezhet majd az értesítésekkel, és hozzáférhet a telefonra, az SMS-ekre, a névjegyekre, a naptárra, a hívásnaplókra és a közeli eszközökre vonatkozó engedélyekhez."</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"Szükség van az alkalmazásra a következő kezeléséhez: <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. A(z) <xliff:g id="APP_NAME">%2$s</xliff:g> műveleteket végezhet majd ezekkel az engedélyekkel:"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"Engedélyezi a(z) „<xliff:g id="APP_NAME">%1$s</xliff:g>” alkalmazás számára az információhoz való hozzáférést a telefonról"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"Többeszközös szolgáltatások"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"A(z) <xliff:g id="APP_NAME">%1$s</xliff:g> engedélyt kér a(z) <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> nevében az alkalmazások eszközök közötti streameléséhez"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Ide tartozhatnak a mikrofonhoz, a kamerához és a helyhez való hozzáférések, valamint a(z) &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt; eszközön érvényes egyéb, bizalmas adatokra vonatkozó hozzáférési engedélyek is.&lt;/p&gt; &lt;p&gt;Ezeket az engedélyeket bármikor módosíthatja a(z) &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt; eszköz beállításai között.&lt;/p&gt;"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"Alkalmazás ikonja"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"További információ gomb"</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"Telefon"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"Címtár"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"Naptár"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"Közeli eszközök"</string>
<string name="permission_storage" msgid="6831099350839392343">"Fotók és médiatartalmak"</string>
<string name="permission_notification" msgid="693762568127741203">"Értesítések"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"Alkalmazások"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"Hozzáférhet telefonszámához és hálózati adataihoz. Hívások és VoIP indításához, hívásátirányításhoz és hívásnaplók szerkesztéséhez szükséges."</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"Olvashatja, létrehozhatja és szerkesztheti a névjegylistánkat, valamint hozzáférhet az eszközén használt összes fiók listájához"</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"Elolvashat minden értesítést, ideértve az olyan információkat, mint a névjegyek, az üzenetek és a fotók"</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"A telefon alkalmazásainak streamelése"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-hy/strings.xml b/packages/CompanionDeviceManager/res/values-hy/strings.xml
index d0b739d0f7cf..acbb453c1a88 100644
--- a/packages/CompanionDeviceManager/res/values-hy/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-hy/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"Թույլատրեք &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; հավելվածին կառավարել ձեր &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; սարքը"</string>
<string name="profile_name_watch" msgid="576290739483672360">"ժամացույց"</string>
<string name="chooser_title" msgid="2262294130493605839">"Ընտրեք <xliff:g id="PROFILE_NAME">%1$s</xliff:g>ը, որը պետք է կառավարվի &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; հավելվածի կողմից"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"Այս հավելվածն անհրաժեշտ է ձեր <xliff:g id="DEVICE_NAME">%1$s</xliff:g> պրոֆիլը կառավարելու համար։ <xliff:g id="APP_NAME">%2$s</xliff:g> հավելվածը կկարողանա փոխազդել ձեր ծանուցումների հետ և կստանա «Հեռախոս», «SMS», «Կոնտակտներ», «Օրացույց», «Կանչերի ցուցակ» և «Մոտակա սարքեր» թույլտվությունները։"</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"Այս հավելվածն անհրաժեշտ է ձեր <xliff:g id="DEVICE_NAME">%1$s</xliff:g> պրոֆիլը կառավարելու համար։ <xliff:g id="APP_NAME">%2$s</xliff:g> հավելվածին կթույլատրվի օգտվել այս թույլտվություններից․"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"Թույլատրեք &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; հավելվածին օգտագործել այս տեղեկությունները ձեր հեռախոսից"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"Միջսարքային ծառայություններ"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> հավելվածը ձեր <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> սարքի անունից թույլտվություն է խնդրում՝ ձեր սարքերի միջև հավելվածներ հեռարձակելու համար"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Դրանք կարող են ներառել խոսափողի, տեսախցիկի և տեղադրության տվյալների օգտագործման թույլտվությունները, ինչպես նաև կոնֆիդենցիալ տեղեկությունների օգտագործման այլ թույլտվություններ &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt; սարքում։&lt;/p&gt; &lt;p&gt;Այդ թույլտվությունները ցանկացած ժամանակ կարելի է փոխել &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt; սարքի ձեր կարգավորումներում։&lt;/p&gt;"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"Հավելվածի պատկերակ"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"«Այլ տեղեկություններ» կոճակ"</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"Հեռախոս"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"Կոնտակտներ"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"Օրացույց"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"Մոտակա սարքեր"</string>
<string name="permission_storage" msgid="6831099350839392343">"Լուսանկարներ և մուլտիմեդիա"</string>
<string name="permission_notification" msgid="693762568127741203">"Ծանուցումներ"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"Հավելվածներ"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"Կարող է օգտագործել ձեր հեռախոսահամարը և ցանցի մասին տեղեկությունները։ Պահանջվում է սովորական և VoIP զանգեր կատարելու, ձայնային փոստի, զանգերի վերահասցեավորման և զանգերի մատյանները փոփոխելու համար"</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"Կարող է կարդալ, ստեղծել և փոփոխել կոնտակտների ցանկը, ինչպես նաև բացել ձեր սարքի բոլոր հաշիվների ցանկը"</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"Կարող է կարդալ բոլոր ծանուցումները, ներառյալ տեղեկությունները, օրինակ՝ կոնտակտները, հաղորդագրությունները և լուսանկարները"</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"Հեռարձակել հեռախոսի հավելվածները"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-in/strings.xml b/packages/CompanionDeviceManager/res/values-in/strings.xml
index ef35e49071d4..9c1c2e0a60a1 100644
--- a/packages/CompanionDeviceManager/res/values-in/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-in/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"Izinkan &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; mengakses &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
<string name="profile_name_watch" msgid="576290739483672360">"smartwatch"</string>
<string name="chooser_title" msgid="2262294130493605839">"Pilih <xliff:g id="PROFILE_NAME">%1$s</xliff:g> untuk dikelola oleh &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"Aplikasi diperlukan untuk mengelola <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> akan diizinkan berinteraksi dengan notifikasi dan mengakses izin Telepon, SMS, Kontak, Kalender, Log panggilan, dan Perangkat di sekitar."</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"Aplikasi diperlukan untuk mengelola <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> akan diizinkan berinteraksi dengan izin ini:"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"Izinkan &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; untuk mengakses informasi ini dari ponsel Anda"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"Layanan lintas perangkat"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> meminta izin atas nama <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> untuk menstreaming aplikasi di antara perangkat Anda"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Ini termasuk akses Mikrofon, Kamera, dan Lokasi, serta izin sensitif lainnya di &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;Anda dapat mengubah izin ini kapan saja di Setelan pada &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"Ikon Aplikasi"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"Tombol Informasi Lainnya"</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"Telepon"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"Kontak"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"Kalender"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"Perangkat di sekitar"</string>
<string name="permission_storage" msgid="6831099350839392343">"Foto dan media"</string>
<string name="permission_notification" msgid="693762568127741203">"Notifikasi"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"Aplikasi"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"Dapat mengakses nomor telepon dan info jaringan. Diperlukan untuk melakukan panggilan dan VoIP, pesan suara, pengalihan panggilan, dan pengeditan log panggilan"</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"Dapat membaca, membuat, atau mengedit daftar kontak, serta mengakses daftar kontak untuk semua akun yang digunakan di perangkat"</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"Dapat membaca semua notifikasi, termasuk informasi seperti kontak, pesan, dan foto"</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"Streaming aplikasi ponsel"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-is/strings.xml b/packages/CompanionDeviceManager/res/values-is/strings.xml
index 9ca64a5099a1..3f8c1c3d3db8 100644
--- a/packages/CompanionDeviceManager/res/values-is/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-is/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"Veita &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; aðgang að &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
<string name="profile_name_watch" msgid="576290739483672360">"úr"</string>
<string name="chooser_title" msgid="2262294130493605839">"Velja <xliff:g id="PROFILE_NAME">%1$s</xliff:g> sem &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; á að stjórna"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"Forritið er nauðsynlegt til að hafa umsjón með <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> fær aðgang að tilkynningum og heimildum síma, SMS, tengiliða, dagatals, símtalaskráa og nálægra tækja."</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"Forritið er nauðsynlegt til að hafa umsjón með <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> fær aðgang að eftirfarandi heimildum:"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"Veita &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; aðgang að þessum upplýsingum úr símanum þínum"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"Þjónustur á milli tækja"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> sendir beiðni um heimild til straumspilunar forrita á milli tækjanna þinna fyrir hönd <xliff:g id="DEVICE_TYPE">%2$s</xliff:g>"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Þetta kann að fela í sér aðgang að hljóðnema, myndavél og staðsetningu og aðrar heimildir fyrir viðkvæmu efni í &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;Hægt er að breyta þessum heimildum hvenær sem er í stillingunum í &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"Tákn forrits"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"Hnappur fyrir upplýsingar"</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"Sími"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"Tengiliðir"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"Dagatal"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"Nálæg tæki"</string>
<string name="permission_storage" msgid="6831099350839392343">"Myndir og efni"</string>
<string name="permission_notification" msgid="693762568127741203">"Tilkynningar"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"Forrit"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"Fær aðgang að símanúmeri og netkerfisupplýsingum. Nauðsynlegt til að hringja símtöl og netsímtöl, fyrir talhólf, framsendingu símtala og breytingar símtalaskráa"</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"Getur lesið, búið til eða breytt tengiliðalista og fær auk þess aðgang að tengiliðalista allra reikninga sem eru notaðir í tækinu"</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"Getur lesið allar tilkynningar, þar á meðal upplýsingar á borð við tengiliði, skilaboð og myndir"</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"Streymdu forritum símans"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-it/strings.xml b/packages/CompanionDeviceManager/res/values-it/strings.xml
index 67ed6b877a25..1d53be9f462a 100644
--- a/packages/CompanionDeviceManager/res/values-it/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-it/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"Consenti all\'app &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; di accedere &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
<string name="profile_name_watch" msgid="576290739483672360">"orologio"</string>
<string name="chooser_title" msgid="2262294130493605839">"Scegli un <xliff:g id="PROFILE_NAME">%1$s</xliff:g> da gestire con &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"Questa app è necessaria per gestire <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> potrà interagire con le tue notifiche e accedere alle autorizzazioni Telefono, SMS, Contatti, Calendario, Registri chiamate e Dispositivi nelle vicinanze."</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"Questa app è necessaria per gestire <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> potrà interagire con le seguenti autorizzazioni:"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"Consenti a &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; di accedere a queste informazioni dal tuo telefono"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"Servizi cross-device"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> richiede per conto del tuo <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> l\'autorizzazione a trasmettere app in streaming tra i dispositivi"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Potrebbero essere incluse le autorizzazioni di accesso al microfono, alla fotocamera e alla posizione, nonché altre autorizzazioni sensibili su &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;Puoi cambiare queste autorizzazioni in qualsiasi momento nelle Impostazioni su &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"Icona dell\'app"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"Pulsante Altre informazioni"</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"Telefono"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"Contatti"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"Calendario"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"Dispositivi nelle vicinanze"</string>
<string name="permission_storage" msgid="6831099350839392343">"Foto e contenuti multimediali"</string>
<string name="permission_notification" msgid="693762568127741203">"Notifiche"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"App"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"Può accedere al tuo numero di telefono e alle informazioni della rete. Necessaria per chiamate e VoIP, segreteria, deviazione delle chiamate e modifica dei registri chiamate"</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"Può leggere, creare o modificare l\'elenco contatti, nonché accedere all\'elenco contatti di tutti gli account usati sul dispositivo"</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"Puoi leggere tutte le notifiche, incluse le informazioni come contatti, messaggi e foto"</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"Trasmetti in streaming le app del tuo telefono"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-iw/strings.xml b/packages/CompanionDeviceManager/res/values-iw/strings.xml
index 8689fea51f42..a169c7873fcb 100644
--- a/packages/CompanionDeviceManager/res/values-iw/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-iw/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"‏אישור לאפליקציה ‎&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&amp;g;‎‏ לגשת אל ‎&lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;‎‏"</string>
<string name="profile_name_watch" msgid="576290739483672360">"שעון"</string>
<string name="chooser_title" msgid="2262294130493605839">"‏בחירת <xliff:g id="PROFILE_NAME">%1$s</xliff:g> לניהול באמצעות &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"‏האפליקציה הזו נחוצה כדי לנהל את <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. האפליקציה <xliff:g id="APP_NAME">%2$s</xliff:g> תוכל לבצע פעולות בהתראות ותקבל הרשאות גישה לטלפון, ל-SMS לאנשי הקשר, ליומן, ליומני השיחות ולמכשירים בקרבת מקום."</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"האפליקציה הזו נחוצה כדי לנהל את <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. האפליקציה <xliff:g id="APP_NAME">%2$s</xliff:g> תוכל לקיים אינטראקציה עם ההרשאות הבאות:"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"‏מתן אישור לאפליקציה &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; לגשת למידע הזה מהטלפון שלך"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"שירותים למספר מכשירים"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"האפליקציה <xliff:g id="APP_NAME">%1$s</xliff:g> מבקשת הרשאה עבור מכשיר <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> כדי לשדר אפליקציות בין המכשירים שלך"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"‏‎&lt;p&gt;‎‏ההרשאות עשויות לכלול גישה למיקרופון, למצלמה ולמיקום, וכן גישה למידע רגיש אחר ב-‎&lt;/strong&gt;‎‏<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>‎&lt;/strong&gt;.&lt;/p&amp;gt‎;‎ ‎&lt;p&gt;אפשר לשנות את ההרשאות האלה בכל שלב בהגדרות של‏ ‎&lt;strong&gt;‎‏<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>‏‎&lt;/strong&gt;.&lt;/p&gt;‎‏"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"סמל האפליקציה"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"לחצן מידע נוסף"</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"טלפון"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"אנשי קשר"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"יומן"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"מכשירים בקרבת מקום"</string>
<string name="permission_storage" msgid="6831099350839392343">"תמונות ומדיה"</string>
<string name="permission_notification" msgid="693762568127741203">"התראות"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"אפליקציות"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"‏אפשרות לגשת למספר הטלפון ופרטי הרשת שלך. הדבר נדרש לצורך ביצוע שיחות ו-VoIP, להודעה קולית, להפניית שיחות ולעריכת יומני שיחות"</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"אפשרות לקרוא, ליצור או לערוך את רשימת אנשי הקשר שלנו, וגם לגשת לרשימה של כל החשבונות שבהם נעשה שימוש במכשיר שלך"</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"גישת קריאה לכל ההתראות, כולל מידע כמו אנשי קשר, הודעות ותמונות."</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"שידור אפליקציות מהטלפון"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-ja/strings.xml b/packages/CompanionDeviceManager/res/values-ja/strings.xml
index f6321b5fa908..686e2ddf61a5 100644
--- a/packages/CompanionDeviceManager/res/values-ja/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ja/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; に &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; へのアクセスを許可"</string>
<string name="profile_name_watch" msgid="576290739483672360">"ウォッチ"</string>
<string name="chooser_title" msgid="2262294130493605839">"&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; の管理対象となる<xliff:g id="PROFILE_NAME">%1$s</xliff:g>の選択"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"このアプリは<xliff:g id="DEVICE_NAME">%1$s</xliff:g>の管理に必要です。<xliff:g id="APP_NAME">%2$s</xliff:g> は、通知の使用と、電話、SMS、連絡先、カレンダー、通話履歴、付近のデバイスの権限へのアクセスが可能となります。"</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"このアプリは<xliff:g id="DEVICE_NAME">%1$s</xliff:g>の管理に必要です。<xliff:g id="APP_NAME">%2$s</xliff:g> は、次の権限の使用が可能となります。"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"スマートフォンのこの情報へのアクセスを &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; に許可"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"クロスデバイス サービス"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> が <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> に代わってデバイス間でアプリをストリーミングする権限をリクエストしています"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;これには、&lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt; のマイク、カメラ、位置情報へのアクセスや、その他の機密情報に関わる権限が含まれる可能性があります。&lt;/p&gt; &lt;p&gt;これらの権限は &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt; の [設定] でいつでも変更できます。&lt;/p&gt;"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"アプリのアイコン"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"詳細情報ボタン"</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"電話"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"連絡先"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"カレンダー"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"付近のデバイス"</string>
<string name="permission_storage" msgid="6831099350839392343">"写真とメディア"</string>
<string name="permission_notification" msgid="693762568127741203">"通知"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"アプリ"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"電話番号とネットワーク情報にアクセスできます。電話と VoIP の発信、ボイスメールの送信、通話のリダイレクト、通話履歴の編集に必要です"</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"連絡先リストの読み取り、作成、編集を行えるほか、デバイスで使用するすべてのアカウントのリストにアクセスできます"</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"連絡先、メッセージ、写真に関する情報を含め、すべての通知を読み取ることができます"</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"スマートフォンのアプリをストリーミングします"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-ka/strings.xml b/packages/CompanionDeviceManager/res/values-ka/strings.xml
index e31ff8a9e6d7..b0ef1c3489d8 100644
--- a/packages/CompanionDeviceManager/res/values-ka/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ka/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"დაუშვით &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>-ის&lt;/strong&gt;, წვდომა თქვენს &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>-ზე&lt;/strong&gt;"</string>
<string name="profile_name_watch" msgid="576290739483672360">"საათი"</string>
<string name="chooser_title" msgid="2262294130493605839">"აირჩიეთ <xliff:g id="PROFILE_NAME">%1$s</xliff:g>, რომელიც უნდა მართოს &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;-მა"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"ეს აპი საჭიროა, რომ მართოთ თქვენი <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> შეძლებს თქვენს შეტყობინებებთან ინტერაქციას და თქვენი ტელეფონის, SMS-ების, კონტაქტებისა, კალენდრის, ზარების ჟურნალისა და ახლომახლო მოწყობილობების ნებართვებზე წვდომას."</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"ეს აპი საჭიროა, რომ მართოთ თქვენი <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> შეძლებს ინტერაქციას შემდეგი ნებართვებით:"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"ნება დართეთ, რომ &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; აპს ჰქონდეს ამ ინფორმაციაზე წვდომა თქვენი ტელეფონიდან"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"მოწყობილობათშორისი სერვისები"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> ითხოვს უფლებას თქვენი <xliff:g id="DEVICE_TYPE">%2$s</xliff:g>-ის სახელით, რომ მოწყობილობებს შორის აპების სტრიმინგი შეძლოს"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;აღნიშნული შეიძლება მოიცავდეს მიკროფონზე, კამერასა და მდებარეობაზე წვდომას თუ სხვა ნებართვას სენსიტიურ ინფორმაციაზე &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;-ში.&lt;/p&gt; &lt;p&gt;ამ ნებართვების შეცვლა ნებისმიერ დროს შეგიძლიათ თქვენი პარამეტრებიდან &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;-ში.&lt;/p&gt;"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"აპის ხატულა"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"დამატებითი ინფორმაციის ღილაკი"</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"Phone"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"კონტაქტები"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"კალენდარი"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"ახლომახლო მოწყობილობები"</string>
<string name="permission_storage" msgid="6831099350839392343">"ფოტოები და მედია"</string>
<string name="permission_notification" msgid="693762568127741203">"შეტყობინებები"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"აპები"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"შეუძლია თქვენი ტელეფონის ნომერსა და ქსელის ინფორმაციაზე წვდომა. საჭიროა ზარების განსახორციელებლად და VoIP-ისთვის, ხმოვანი ფოსტისთვის, ზარის გადამისამართებისა და ზარების ჟურნალების რედაქტირებისთვის"</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"შეუძლია ჩვენი კონტაქტების სიის წაკითხვა, შექმნა ან რედაქტირება, ასევე, თქვენს მოწყობილობაზე გამოყენებული ყველა ანგარიშის კონტაქტების სიაზე წვდომა"</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"შეუძლია წაიკითხოს ყველა შეტყობინება, მათ შორის ისეთი ინფორმაცია, როგორიცაა კონტაქტები, ტექსტური შეტყობინებები და ფოტოები"</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"თქვენი ტელეფონის აპების სტრიმინგი"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-kk/strings.xml b/packages/CompanionDeviceManager/res/values-kk/strings.xml
index f82a1d517942..18ab58087363 100644
--- a/packages/CompanionDeviceManager/res/values-kk/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-kk/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; қолданбасына &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; құрылғысын пайдалануға рұқсат беру"</string>
<string name="profile_name_watch" msgid="576290739483672360">"сағат"</string>
<string name="chooser_title" msgid="2262294130493605839">"&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; арқылы басқарылатын <xliff:g id="PROFILE_NAME">%1$s</xliff:g> құрылғысын таңдаңыз"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"Қолданба <xliff:g id="DEVICE_NAME">%1$s</xliff:g> құрылғысын басқару үшін қажет. <xliff:g id="APP_NAME">%2$s</xliff:g> қолданбасына хабарландыруларға қатысты әрекет жасау, телефон, SMS, контактілер, күнтізбе, қоңырау журналдары қолданбаларын және маңайдағы құрылғыларды пайдалану рұқсаттары беріледі."</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"Қолданба <xliff:g id="DEVICE_NAME">%1$s</xliff:g> құрылғысын басқару үшін қажет. <xliff:g id="APP_NAME">%2$s</xliff:g> қолданбасына осы рұқсаттар беріледі:"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; қолданбасына телефоныңыздағы осы ақпаратты пайдалануға рұқсат беріңіз."</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"Аралық құрылғы қызметтері"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> қолданбасы <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> атынан құрылғылар арасында қолданбалар трансляциялау үшін рұқсат сұрайды."</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Оларға микрофонды, камераны және геодеректі пайдалану рұқсаттары, сондай-ақ &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt; құрылғысына берілетін басқа да құпия ақпарат рұқсаттары кіруі мүмкін.&lt;/p&gt; &lt;p&gt;Бұл рұқсаттарды кез келген уақытта &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt; құрылғысындағы параметрлерден өзгерте аласыз.&lt;/p&gt;"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"Қолданба белгішесі"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"\"Қосымша ақпарат\" түймесі"</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"Телефон"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"Контактілер"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"Күнтізбе"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"Маңайдағы құрылғылар"</string>
<string name="permission_storage" msgid="6831099350839392343">"Фотосуреттер мен медиафайлдар"</string>
<string name="permission_notification" msgid="693762568127741203">"Хабарландырулар"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"Қолданбалар"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"Телефон нөміріңізге және желі ақпаратына қол жеткізе алады. Қоңырау шалу, VoIP, дауыстық хабар жіберу, қоңырау бағытын ауыстыру және қоңырау журналдарын өзгерту үшін қажет."</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"Контактілер тізімін оқуға, жасауға немесе өзгертуге, сондай-ақ құрылғыңызда қолданылатын барлық аккаунт тізімін пайдалануға рұқсат беріледі."</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"Барлық хабарландыруды, соның ішінде контактілер, хабарлар және фотосуреттер сияқты ақпаратты оқи алады."</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"Телефон қолданбаларын трансляциялайды."</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-km/strings.xml b/packages/CompanionDeviceManager/res/values-km/strings.xml
index 07a195a46237..c90b3a202a33 100644
--- a/packages/CompanionDeviceManager/res/values-km/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-km/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"អនុញ្ញាតឱ្យ &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ចូលប្រើប្រាស់ &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; របស់អ្នក"</string>
<string name="profile_name_watch" msgid="576290739483672360">"នាឡិកា"</string>
<string name="chooser_title" msgid="2262294130493605839">"ជ្រើសរើស <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ដើម្បីឱ្យស្ថិតក្រោម​ការគ្រប់គ្រងរបស់ &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"ត្រូវការកម្មវិធីនេះ ដើម្បីគ្រប់គ្រង <xliff:g id="DEVICE_NAME">%1$s</xliff:g> របស់អ្នក។ <xliff:g id="APP_NAME">%2$s</xliff:g> នឹងត្រូវបានអនុញ្ញាតឱ្យ​ធ្វើអន្តរកម្មជាមួយ​ការជូនដំណឹងរបស់អ្នក និងចូលប្រើការអនុញ្ញាតទូរសព្ទ, SMS, ទំនាក់ទំនង, ប្រតិទិន, កំណត់​ហេតុ​ហៅ​ទូរសព្ទ និងឧបករណ៍នៅជិតរបស់អ្នក។"</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"ត្រូវការកម្មវិធីនេះ ដើម្បីគ្រប់គ្រង <xliff:g id="DEVICE_NAME">%1$s</xliff:g> របស់អ្នក។ <xliff:g id="APP_NAME">%2$s</xliff:g> នឹងត្រូវបានអនុញ្ញាតឱ្យធ្វើអន្តរកម្មជាមួយការអនុញ្ញាតទាំងនេះ៖"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"អនុញ្ញាតឱ្យ &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ចូលប្រើព័ត៌មាននេះពីទូរសព្ទរបស់អ្នក"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"សេវាកម្មឆ្លងកាត់ឧបករណ៍"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> កំពុងស្នើសុំការអនុញ្ញាតជំនួសឱ្យ <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> របស់អ្នក ដើម្បីបញ្ចាំងកម្មវិធីរវាងឧបករណ៍របស់អ្នក"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;សកម្មភាពនេះ​អាចរួមបញ្ចូល​ការចូលប្រើ​ទីតាំង កាមេរ៉ា និងមីក្រូហ្វូន និងការអនុញ្ញាត​ដែលមានលក្ខណៈ​រសើបផ្សេងទៀត​នៅលើ &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;។&lt;/p&gt; &lt;p&gt;អ្នកអាចប្ដូរ​ការអនុញ្ញាតទាំងនេះ​បានគ្រប់ពេលវេលា​នៅក្នុងការកំណត់​នៅលើ &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;។&lt;/p&gt;"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"រូប​កម្មវិធី"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"ប៊ូតុងព័ត៌មានបន្ថែម"</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"ទូរសព្ទ"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"Contacts"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"ប្រតិទិន"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"ឧបករណ៍នៅជិត"</string>
<string name="permission_storage" msgid="6831099350839392343">"រូបថត និងមេឌៀ"</string>
<string name="permission_notification" msgid="693762568127741203">"ការ​ជូនដំណឹង"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"កម្មវិធី"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"អាចចូលមើលលេខទូរសព្ទ និងព័ត៌មានបណ្ដាញរបស់អ្នក។ ត្រូវបានតម្រូវសម្រាប់ការហៅទូរសព្ទនិង VoIP, សារជាសំឡេង, ការបញ្ជូនបន្តការហៅទូរសព្ទ និងការកែកំណត់ហេតុហៅទូរសព្ទ"</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"អាចអាន បង្កើត ឬកែបញ្ជីទំនាក់ទំនងរបស់យើង ក៏ដូចជាចូលប្រើបញ្ជីគណនីទាំងអស់ដែលត្រូវបានប្រើនៅលើឧបករណ៍របស់អ្នក"</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"អាចអាន​ការជូនដំណឹង​ទាំងអស់ រួមទាំង​ព័ត៌មាន​ដូចជាទំនាក់ទំនង សារ និងរូបថត"</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"ផ្សាយកម្មវិធីរបស់ទូរសព្ទអ្នក"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-kn/strings.xml b/packages/CompanionDeviceManager/res/values-kn/strings.xml
index b453f3b0be67..f60f7bc9ebb9 100644
--- a/packages/CompanionDeviceManager/res/values-kn/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-kn/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"ನಿಮ್ಮ &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; ಅನ್ನು ಪ್ರವೇಶಿಸಲು &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ಗೆ ಅನುಮತಿಸಿ"</string>
<string name="profile_name_watch" msgid="576290739483672360">"ವೀಕ್ಷಿಸಿ"</string>
<string name="chooser_title" msgid="2262294130493605839">"&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; ಮೂಲಕ ನಿರ್ವಹಿಸಬೇಕಾದ <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ಅನ್ನು ಆಯ್ಕೆಮಾಡಿ"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"ನಿಮ್ಮ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ಅನ್ನು ನಿರ್ವಹಿಸಲು ಆ್ಯಪ್‌ನ ಅಗತ್ಯವಿದೆ. ನಿಮ್ಮ ಅಧಿಸೂಚನೆಗಳೊಂದಿಗೆ ಸಂವಹನ ನಡೆಸಲು ಮತ್ತು ನಿಮ್ಮ ಫೋನ್, SMS, ಸಂಪರ್ಕಗಳು, Calendar, ಕರೆಯ ಲಾಗ್‌ಗಳು ಹಾಗೂ ಸಮೀಪದಲ್ಲಿರುವ ಸಾಧನಗಳ ಅನುಮತಿಗಳನ್ನು ಪ್ರವೇಶಿಸಲು <xliff:g id="APP_NAME">%2$s</xliff:g> ಗೆ ಅನುಮತಿಸಲಾಗುತ್ತದೆ."</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"ನಿಮ್ಮ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ಅನ್ನು ನಿರ್ವಹಿಸಲು ಆ್ಯಪ್‌ನ ಅಗತ್ಯವಿದೆ. <xliff:g id="APP_NAME">%2$s</xliff:g> ಗೆ ಈ ಅನುಮತಿಗಳೊಂದಿಗೆ ಸಂವಹನ ನಡೆಸಲು ಅನುಮತಿಸಲಾಗುವುದು:"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"ನಿಮ್ಮ ಫೋನ್ ಮೂಲಕ ಈ ಮಾಹಿತಿಯನ್ನು ಆ್ಯಕ್ಸೆಸ್ ಮಾಡಲು &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ಗೆ ಅನುಮತಿಸಿ"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"ಕ್ರಾಸ್-ಡಿವೈಸ್ ಸೇವೆಗಳು"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"ನಿಮ್ಮ ಸಾಧನಗಳ ನಡುವೆ ಆ್ಯಪ್‌ಗಳನ್ನು ಸ್ಟ್ರೀಮ್ ಮಾಡಲು ನಿಮ್ಮ <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> ನ ಪರವಾಗಿ <xliff:g id="APP_NAME">%1$s</xliff:g> ಅನುಮತಿಯನ್ನು ವಿನಂತಿಸಿಕೊಳ್ಳುತ್ತಿದೆ"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;ಇದು &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt; ನಲ್ಲಿನ ಮೈಕ್ರೊಫೋನ್, ಕ್ಯಾಮರಾ ಮತ್ತು ಸ್ಥಳ ಆ್ಯಕ್ಸೆಸ್ ಹಾಗೂ ಇತರ ಸೂಕ್ಷ್ಮ ಅನುಮತಿಗಳನ್ನು ಹೊಂದಿರಬಹುದು&lt;p&gt;&lt;/p&gt; &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt; ನಲ್ಲಿನ ನಿಮ್ಮ ಸೆಟ್ಟಿಂಗ್‌ಗಳಲ್ಲಿ ನೀವು ಈ ಅನುಮತಿಗಳನ್ನು ಯಾವಾಗ ಬೇಕಾದರೂ ಬದಲಾಯಿಸಬಹುದು.&lt;/p&gt;"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"ಆ್ಯಪ್ ಐಕಾನ್"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"ಹೆಚ್ಚಿನ ಮಾಹಿತಿಯ ಬಟನ್"</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"ಫೋನ್"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"ಸಂಪರ್ಕಗಳು"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"Calendar"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"ಸಮೀಪದಲ್ಲಿರುವ ಸಾಧನಗಳು"</string>
<string name="permission_storage" msgid="6831099350839392343">"ಫೋಟೋಗಳು ಮತ್ತು ಮಾಧ್ಯಮ"</string>
<string name="permission_notification" msgid="693762568127741203">"ಅಧಿಸೂಚನೆಗಳು"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"ಆ್ಯಪ್‌ಗಳು"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"ನಿಮ್ಮ ಫೋನ್ ಸಂಖ್ಯೆ ಮತ್ತು ನೆಟ್‌ವರ್ಕ್ ಮಾಹಿತಿಯನ್ನು ಪ್ರವೇಶಿಸಬಹುದು. ಕರೆಗಳನ್ನು ಮಾಡಲು ಮತ್ತು VoIP, ಧ್ವನಿಮೇಲ್, ಕರೆ ಮರುನಿರ್ದೇಶನ ಮತ್ತು ಕರೆಯ ಲಾಗ್‌ಗಳನ್ನು ಎಡಿಟ್ ಮಾಡಲು ಅಗತ್ಯವಿದೆ"</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"ನಮ್ಮ ಸಂಪರ್ಕ ಪಟ್ಟಿಯನ್ನು ಓದಬಹುದು, ರಚಿಸಬಹುದು ಅಥವಾ ಎಡಿಟ್ ಮಾಡಬಹುದು, ಹಾಗೆಯೇ ನಿಮ್ಮ ಸಾಧನದಲ್ಲಿ ಬಳಸಲಾದ ಎಲ್ಲಾ ಖಾತೆಗಳ ಪಟ್ಟಿಯನ್ನು ಪ್ರವೇಶಿಸಬಹುದು"</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"ಸಂಪರ್ಕಗಳು, ಸಂದೇಶಗಳು ಮತ್ತು ಫೋಟೋಗಳಂತಹ ಮಾಹಿತಿಯನ್ನು ಒಳಗೊಂಡಂತೆ ಎಲ್ಲಾ ಅಧಿಸೂಚನೆಗಳನ್ನು ಓದಬಹುದು"</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"ನಿಮ್ಮ ಫೋನ್‍ನ ಆ್ಯಪ್‌ಗಳನ್ನು ಸ್ಟ್ರೀಮ್ ಮಾಡಿ"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-ko/strings.xml b/packages/CompanionDeviceManager/res/values-ko/strings.xml
index 361d3b2f5e7c..3442ab28ae88 100644
--- a/packages/CompanionDeviceManager/res/values-ko/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ko/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;에서 내 &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; 기기에 액세스하도록 허용"</string>
<string name="profile_name_watch" msgid="576290739483672360">"시계"</string>
<string name="chooser_title" msgid="2262294130493605839">"&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;에서 관리할 <xliff:g id="PROFILE_NAME">%1$s</xliff:g>을(를) 선택"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"이 앱은 <xliff:g id="DEVICE_NAME">%1$s</xliff:g> 기기를 관리하는 데 필요합니다. <xliff:g id="APP_NAME">%2$s</xliff:g>에서 알림과 상호작용하고 내 전화, SMS, 연락처, Calendar, 통화 기록, 근처 기기에 대한 권한을 갖게 됩니다."</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"이 앱은 <xliff:g id="DEVICE_NAME">%1$s</xliff:g> 기기를 관리하는 데 필요합니다. <xliff:g id="APP_NAME">%2$s</xliff:g>에서 다음 권한과 상호작용할 수 있습니다."</string>
<string name="title_app_streaming" msgid="2270331024626446950">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;이 휴대전화의 이 정보에 액세스하도록 허용합니다."</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"교차 기기 서비스"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g>에서 <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> 대신 기기 간에 앱을 스트리밍할 수 있는 권한을 요청하고 있습니다."</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;여기에는 &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;에서 허용했던 마이크, 카메라, 위치 정보 액세스 권한 및 기타 민감한 권한이 포함될 수 있습니다.&lt;/p&gt; &lt;p&gt;언제든지 &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;의 설정에서 이러한 권한을 변경할 수 있습니다.&lt;/p&gt;"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"앱 아이콘"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"추가 정보 버튼"</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"전화"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"연락처"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"캘린더"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"근처 기기"</string>
<string name="permission_storage" msgid="6831099350839392343">"사진 및 미디어"</string>
<string name="permission_notification" msgid="693762568127741203">"알림"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"앱"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"전화번호 및 네트워크 정보에 액세스할 수 있습니다. 전화 걸기 및 VoIP, 음성사서함, 통화 리디렉션, 통화 기록 수정 시 필요합니다."</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"연락처 목록을 읽고, 만들고, 수정할 수 있으며 기기에서 사용되는 모든 계정의 목록에도 액세스할 수 있습니다."</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"연락처, 메시지, 사진 등의 정보를 포함한 모든 알림을 읽을 수 있습니다."</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"휴대전화의 앱을 스트리밍합니다."</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-ky/strings.xml b/packages/CompanionDeviceManager/res/values-ky/strings.xml
index 57b2747d44a3..36a72ff35b11 100644
--- a/packages/CompanionDeviceManager/res/values-ky/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ky/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; колдонмосуна &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; түзмөгүңүзгө кирүүгө уруксат бериңиз"</string>
<string name="profile_name_watch" msgid="576290739483672360">"саат"</string>
<string name="chooser_title" msgid="2262294130493605839">"<xliff:g id="PROFILE_NAME">%1$s</xliff:g> &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; тарабынан башкарылсын"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"Бул колдонмо <xliff:g id="DEVICE_NAME">%1$s</xliff:g> түзмөгүңүздү тескөө үчүн керек. <xliff:g id="APP_NAME">%2$s</xliff:g> билдирмелериңизди көрүп, телефонуңуз, SMS билдирүүлөр, байланыштар, жылнаама, чалуулар тизмеси жана жакын жердеги түзмөктөргө болгон уруксаттарды пайдалана алат."</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"Бул колдонмо <xliff:g id="DEVICE_NAME">%1$s</xliff:g> түзмөгүңүздү тескөө үчүн керек. <xliff:g id="APP_NAME">%2$s</xliff:g> төмөнкү уруксаттарды пайдалана алат:"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; колдонмосуна телефонуңуздагы ушул маалыматты көрүүгө уруксат бериңиз"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"Түзмөктөр аралык кызматтар"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> түзмөгүңүздүн атынан түзмөктөрүңүздүн ортосунда колдонмолорду өткөрүүгө уруксат сурап жатат"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Бул уруксаттарга микрофонго, камерага жана жайгашкан жерге кирүү мүмкүнчүлүгү жана &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt; түзмөгүндөгү башка купуя маалыматты көрүүгө уруксаттар кириши мүмкүн.&lt;/p&gt; &lt;p&gt;Бул уруксаттарды каалаган убакта &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt; түзмөгүндөгү Жөндөөлөрдөн өзгөртө аласыз.&lt;/p&gt;"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"Колдонмонун сүрөтчөсү"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"Дагы маалымат баскычы"</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"Телефон"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"Байланыштар"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"Жылнаама"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"Жакын жердеги түзмөктөр"</string>
<string name="permission_storage" msgid="6831099350839392343">"Сүрөттөр жана медиафайлдар"</string>
<string name="permission_notification" msgid="693762568127741203">"Билдирмелер"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"Колдонмолор"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"Телефонуңуздун номерин жана тармак тууралуу маалыматты көрө алат. Чалуу жана VoIP, үн почтасы, чалууларды багыттоо жана чалуулар тизмесин түзөтүү үчүн талап кылынат"</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"Байланыштар тизмесин окуп, түзүп же түзөтүп жана түзмөгүңүздөгү бардык аккаунттардагы тизмелерге кире алат"</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"Бардык билдирмелерди, анын ичинде байланыштар, билдирүүлөр жана сүрөттөр сыяктуу маалыматты окуй алат"</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"Телефондогу колдонмолорду алып ойнотуу"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-lo/strings.xml b/packages/CompanionDeviceManager/res/values-lo/strings.xml
index 9ec713638750..9283eb79b53f 100644
--- a/packages/CompanionDeviceManager/res/values-lo/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-lo/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"ອະນຸຍາດ &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ໃຫ້ເຂົ້າເຖິງ &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; ຂອງທ່ານໄດ້"</string>
<string name="profile_name_watch" msgid="576290739483672360">"ໂມງ"</string>
<string name="chooser_title" msgid="2262294130493605839">"ເລືອກ <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ເພື່ອໃຫ້ຖືກຈັດການໂດຍ &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"ຕ້ອງໃຊ້ແອັບດັ່ງກ່າວເພື່ອຈັດການ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ຂອງທ່ານ. <xliff:g id="APP_NAME">%2$s</xliff:g> ຈະໄດ້ຮັບອະນຸຍາດໃຫ້ໂຕ້ຕອບກັບການແຈ້ງເຕືອນຂອງທ່ານ ແລະ ເຂົ້າເຖິງການອະນຸຍາດໂທລະສັບ, SMS, ລາຍຊື່ຜູ້ຕິດຕໍ່, ປະຕິທິນ, ບັນທຶກການໂທ ແລະ ອຸປະກອນທີ່ຢູ່ໃກ້ຄຽງຂອງທ່ານ."</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"ຕ້ອງໃຊ້ແອັບດັ່ງກ່າວເພື່ອຈັດການ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ຂອງທ່ານ. <xliff:g id="APP_NAME">%2$s</xliff:g> ຈະໄດ້ຮັບອະນຸຍາດໃຫ້ໂຕ້ຕອບກັບການອະນຸຍາດເຫຼົ່ານີ້:"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"ອະນຸຍາດ &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ໃຫ້ເຂົ້າເຖິງຂໍ້ມູນນີ້ຈາກໂທລະສັບຂອງທ່ານໄດ້"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"ບໍລິການຂ້າມອຸປະກອນ"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> ກຳລັງຮ້ອງຂໍການອະນຸຍາດໃນນາມຂອງ <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> ເພື່ອສະຕຣີມແອັບລະຫວ່າງອຸປະກອນຂອງທ່ານ"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;ນີ້ອາດຮວມສິດເຂົ້າເຖິງໄມໂຄຣໂຟນ, ກ້ອງຖ່າຍຮູບ ແລະ ສະຖານທີ່, ຮວມທັງການອະນຸຍາດທີ່ລະອຽດອ່ອນອື່ນໆຢູ່ &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;ທ່ານສາມາດປ່ຽນການອະນຸຍາດເຫຼົ່ານີ້ຕອນໃດກໍໄດ້ໃນການຕັ້ງຄ່າຂອງທ່ານຢູ່ &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"ໄອຄອນແອັບ"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"ປຸ່ມຂໍ້ມູນເພີ່ມເຕີມ"</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"ໂທລະສັບ"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"ລາຍຊື່ຜູ້ຕິດຕໍ່"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"ປະຕິທິນ"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"ອຸປະກອນທີ່ຢູ່ໃກ້ຄຽງ"</string>
<string name="permission_storage" msgid="6831099350839392343">"ຮູບພາບ ແລະ ມີເດຍ"</string>
<string name="permission_notification" msgid="693762568127741203">"ການແຈ້ງເຕືອນ"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"ແອັບ"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"ສາມາດເຂົ້າເຖິງເບີໂທລະສັບ ແລະ ຂໍ້ມູນເຄືອຂ່າຍຂອງທ່ານ. ເຊິ່ງຈຳເປັນສຳລັບການໂທ ແລະ VoIP, ຂໍ້ຄວາມສຽງ, ການປ່ຽນເສັ້ນທາງການໂທ ແລະ ການແກ້ໄຂບັນທຶກການໂທ"</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"ສາມາດອ່ານ, ສ້າງ ຫຼື ແກ້ໄຂລາຍຊື່ຜູ້ຕິດຕໍ່ຂອງພວກເຮົາ, ຮວມທັງເຂົ້າເຖິງລາຍຊື່ຂອງບັນຊີທັງໝົດທີ່ໃຊ້ຢູ່ໃນອຸປະກອນຂອງທ່ານ"</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"ສາມາດອ່ານການແຈ້ງເຕືອນທັງໝົດ, ຮວມທັງຂໍ້ມູນ ເຊັ່ນ: ລາຍຊື່ຜູ້ຕິດຕໍ່, ຂໍ້ຄວາມ ແລະ ຮູບພາບ"</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"ສະຕຣີມແອັບຂອງໂທລະສັບທ່ານ"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-lt/strings.xml b/packages/CompanionDeviceManager/res/values-lt/strings.xml
index 664059527e48..3483cf31422f 100644
--- a/packages/CompanionDeviceManager/res/values-lt/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-lt/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"Leisti &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; pasiekti jūsų &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
<string name="profile_name_watch" msgid="576290739483672360">"laikrodį"</string>
<string name="chooser_title" msgid="2262294130493605839">"Jūsų <xliff:g id="PROFILE_NAME">%1$s</xliff:g>, kurį valdys &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; (pasirinkite)"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"Programa reikalinga norint tvarkyti jūsų <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. „<xliff:g id="APP_NAME">%2$s</xliff:g>“ bus leidžiama sąveikauti su pranešimų funkcija ir pasiekti Telefono, SMS, Kontaktų, Kalendoriaus, Skambučių žurnalų ir Įrenginių netoliese leidimus."</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"Programa reikalinga norint tvarkyti jūsų <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. „<xliff:g id="APP_NAME">%2$s</xliff:g>“ bus leidžiama sąveikauti su toliau nurodytais leidimais."</string>
<string name="title_app_streaming" msgid="2270331024626446950">"Leisti &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; pasiekti šią informaciją iš jūsų telefono"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"Pasl. keliuose įrenginiuose"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"Programa „<xliff:g id="APP_NAME">%1$s</xliff:g>“ prašo leidimo jūsų „<xliff:g id="DEVICE_TYPE">%2$s</xliff:g>“ vardu, kad galėtų srautu perduoti programas iš vieno įrenginio į kitą"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Gali būti įtraukti prieigos prie mikrofono, kameros ir vietovės leidimai ir kiti leidimai pasiekti neskelbtiną informaciją &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt; įrenginyje.&lt;/p&gt; &lt;p&gt;Šiuos leidimus galite bet kada pakeisti „Nustatymų“ skiltyje &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt; įrenginyje.&lt;/p&gt;"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"Programos piktograma"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"Mygtukas „Daugiau informacijos“"</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"Telefonas"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"Kontaktai"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"Kalendorius"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"Įrenginiai netoliese"</string>
<string name="permission_storage" msgid="6831099350839392343">"Nuotraukos ir medija"</string>
<string name="permission_notification" msgid="693762568127741203">"Pranešimai"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"Programos"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"Galima pasiekti telefono numerio ir tinklo informaciją. Reikalinga norint skambinti ir naudoti „VoIP“, balso paštą, skambučių peradresavimo funkciją bei redaguoti skambučių žurnalus"</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"Galima skaityti, kurti ar redaguoti kontaktų sąrašą bei pasiekti visų įrenginyje naudojamų paskyrų sąrašą"</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"Galima skaityti visus pranešimus, įskaitant tokią informaciją kaip kontaktai, pranešimai ir nuotraukos"</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"Telefono programų perdavimas srautu"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-lv/strings.xml b/packages/CompanionDeviceManager/res/values-lv/strings.xml
index ae246363c0ba..039cc4de4780 100644
--- a/packages/CompanionDeviceManager/res/values-lv/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-lv/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"Atļauja lietotnei &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; piekļūt jūsu ierīcei &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
<string name="profile_name_watch" msgid="576290739483672360">"pulkstenis"</string>
<string name="chooser_title" msgid="2262294130493605839">"Profila (<xliff:g id="PROFILE_NAME">%1$s</xliff:g>) izvēle, ko pārvaldīt lietotnē &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"Šī lietotne ir nepieciešama jūsu ierīces (<xliff:g id="DEVICE_NAME">%1$s</xliff:g>) pārvaldībai. Lietotnei <xliff:g id="APP_NAME">%2$s</xliff:g> tiks atļauts mijiedarboties ar jūsu paziņojumiem un piekļūt šādām atļaujām: Tālrunis, Īsziņas, Kontaktpersonas, Kalendārs, Zvanu žurnāli un Tuvumā esošas ierīces."</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"Šī lietotne ir nepieciešama jūsu ierīces (<xliff:g id="DEVICE_NAME">%1$s</xliff:g>) pārvaldībai. Lietotnei <xliff:g id="APP_NAME">%2$s</xliff:g> tiks atļauts mijiedarboties ar šīm atļaujām:"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"Atļaut lietotnei &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; piekļūt šai informācijai no jūsu tālruņa"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"Vairāku ierīču pakalpojumi"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> pieprasa atļauju straumēt lietotnes starp jūsu ierīcēm šīs ierīces vārdā: <xliff:g id="DEVICE_TYPE">%2$s</xliff:g>"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Tās var būt mikrofona, kameras, atrašanās vietas piekļuves atļaujas un citas sensitīvas atļaujas ierīcē &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;Atļaujas jebkurā brīdī varat mainīt ierīces &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; iestatījumos."</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"Lietotnes ikona"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"Plašākas informācijas poga"</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"Tālrunis"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"Īsziņas"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"Kontaktpersonas"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"Kalendārs"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"Tuvumā esošas ierīces"</string>
<string name="permission_storage" msgid="6831099350839392343">"Fotoattēli un multivides faili"</string>
<string name="permission_notification" msgid="693762568127741203">"Paziņojumi"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"Lietotnes"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"Var piekļūt jūsu tālruņa numuram un tīkla informācijai. Nepieciešama, lai veiktu zvanus un VoIP zvanus, izmantotu balss pastu, pāradresētu zvanus un rediģētu zvanu žurnālus."</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"Var lasīt, izveidot vai rediģēt jūsu kontaktpersonu sarakstu, kā arī piekļūt visu jūsu ierīcē izmantoto kontu kontaktpersonu sarakstiem"</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"Var lasīt visus paziņojumus, tostarp tādu informāciju kā kontaktpersonas, ziņojumi un fotoattēli."</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"Straumēt jūsu tālruņa lietotnes"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-mk/strings.xml b/packages/CompanionDeviceManager/res/values-mk/strings.xml
index cdecb20c6115..938932dcdb62 100644
--- a/packages/CompanionDeviceManager/res/values-mk/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-mk/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"Дозволете &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; да пристапува до вашиот &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
<string name="profile_name_watch" msgid="576290739483672360">"часовник"</string>
<string name="chooser_title" msgid="2262294130493605839">"Изберете <xliff:g id="PROFILE_NAME">%1$s</xliff:g> со којшто ќе управува &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"Апликацијата е потребна за управување со вашиот <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> ќе може да остварува интеракција со известувањата и да пристапува до дозволите за „Телефон“, SMS, „Контакти“, „Календар“, „Евиденција на повици“ и „Уреди во близина“."</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"Апликацијата е потребна за управување со вашиот <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> ќе може да остварува интеракција со следниве дозволи:"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"Овозможете &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; да пристапува до овие податоци на телефонот"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"Повеќенаменски услуги"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> бара дозвола во име на вашиот <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> за да стримува апликации помеѓу вашите уреди"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Ова може да вклучува пристап до микрофон, камера и локација и други чувствителни дозволи на &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;Може да ги промените дозволиве во секое време во вашите „Поставки“ на &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"Икона на апликацијата"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"Копче за повеќе информации"</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"Телефон"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"Контакти"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"Календар"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"Уреди во близина"</string>
<string name="permission_storage" msgid="6831099350839392343">"Аудиовизуелни содржини"</string>
<string name="permission_notification" msgid="693762568127741203">"Известувања"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"Апликации"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"Може да пристапува до вашиот телефонски број и податоците за мрежата. Потребно за упатување повици и VoIP, говорна пошта, пренасочување повици и изменување евиденција на повици"</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"Може да го чита, создава или изменува вашиот список со контакти, како и да пристапува до списоците со контакти на сите сметки што се користат на уредот"</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"може да ги чита сите известувања, вклучително и податоци како контакти, пораки и фотографии"</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"Стримувајте ги апликациите на телефонот"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-mn/strings.xml b/packages/CompanionDeviceManager/res/values-mn/strings.xml
index 8361bd8009e1..511e9eb73478 100644
--- a/packages/CompanionDeviceManager/res/values-mn/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-mn/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"&lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;-д таны &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;-д хандахыг зөвшөөрнө үү"</string>
<string name="profile_name_watch" msgid="576290739483672360">"цаг"</string>
<string name="chooser_title" msgid="2262294130493605839">"&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;-н удирдах<xliff:g id="PROFILE_NAME">%1$s</xliff:g>-г сонгоно уу"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"Энэ апп таны <xliff:g id="DEVICE_NAME">%1$s</xliff:g>-г удирдахад шаардлагатай. <xliff:g id="APP_NAME">%2$s</xliff:g>-д таны мэдэгдэлтэй харилцан үйлдэл хийж, Утас, SMS, Харилцагчид, Календарь, Дуудлагын жагсаалт болон Ойролцоох төхөөрөмжүүдийн зөвшөөрөлд хандахыг зөвшөөрнө."</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"Энэ апп таны <xliff:g id="DEVICE_NAME">%1$s</xliff:g>-г удирдахад шаардлагатай. <xliff:g id="APP_NAME">%2$s</xliff:g>-д эдгээр зөвшөөрөлтэй харилцан үйлдэл хийхийг зөвшөөрнө:"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;-д таны утаснаас энэ мэдээлэлд хандахыг зөвшөөрнө үү"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"Төхөөрөмж хоорондын үйлчилгээ"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"Таны төхөөрөмжүүд хооронд апп дамжуулахын тулд <xliff:g id="APP_NAME">%1$s</xliff:g> таны <xliff:g id="DEVICE_TYPE">%2$s</xliff:g>-н өмнөөс зөвшөөрөл хүсэж байна"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Үүнд Микрофон, Камер болон Байршлын хандалт болон &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt; дээрх бусад эмзэг зөвшөөрөл багтаж болно.&lt;/p&gt; &lt;p&gt;Та эдгээр зөвшөөрлийг &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt; дээрх Тохиргоо хэсэгтээ хүссэн үедээ өөрчлөх боломжтой.&lt;/p&gt;"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"Aппын дүрс тэмдэг"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"Дэлгэрэнгүй мэдээллийн товчлуур"</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"Утас"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"Харилцагчид"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"Календарь"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"Ойролцоох төхөөрөмжүүд"</string>
<string name="permission_storage" msgid="6831099350839392343">"Зураг болон медиа"</string>
<string name="permission_notification" msgid="693762568127741203">"Мэдэгдэл"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"Аппууд"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"Таны утасны дугаар болон сүлжээний мэдээлэлд хандах боломжтой. Дуудлага хийх, VoIP, дуут шуудан, дуудлагыг дахин чиглүүлэх болон дуудлагын жагсаалтыг засахад шаардлагатай"</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"Манай харилцагчийн жагсаалтыг унших, үүсгэх эсвэл засахаас гадна таны төхөөрөмж дээр ашигласан бүх бүртгэлийн жагсаалтад хандах боломжтой"</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"Харилцагчид, мессеж болон зураг зэрэг мэдээллийг оруулаад бүх мэдэгдлийг унших боломжтой"</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"Утасныхаа аппуудыг дамжуулаарай"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-mr/strings.xml b/packages/CompanionDeviceManager/res/values-mr/strings.xml
index 65be3671f8d1..37a83497c67b 100644
--- a/packages/CompanionDeviceManager/res/values-mr/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-mr/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"तुमचे &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; अ‍ॅक्सेस करण्यासाठी &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ला अनुमती द्या"</string>
<string name="profile_name_watch" msgid="576290739483672360">"वॉच"</string>
<string name="chooser_title" msgid="2262294130493605839">"&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; द्वारे व्यवस्थापित करण्यासाठी <xliff:g id="PROFILE_NAME">%1$s</xliff:g> निवडा"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"तुमचे <xliff:g id="DEVICE_NAME">%1$s</xliff:g> व्यवस्थापित करण्यासाठी हे ॲप आवश्यक आहे. <xliff:g id="APP_NAME">%2$s</xliff:g> ला तुमच्या सूचनांशी संवाद साधण्याची आणि तुमचा फोन, एसएमएस, संपर्क कॅलेंडर, कॉल लॉग व जवळपासच्या डिव्हाइसच्या परवानग्या अ‍ॅक्सेस करण्याची अनुमती मिळेल."</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"तुमचे <xliff:g id="DEVICE_NAME">%1$s</xliff:g> व्यवस्थापित करण्यासाठी हे ॲप आवश्यक आहे. <xliff:g id="APP_NAME">%2$s</xliff:g> ला पुढील परवानग्यांशी संवाद साधण्याची अनुमती मिळेल:"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ला ही माहिती तुमच्या फोनवरून अ‍ॅक्सेस करण्यासाठी अनुमती द्या"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"क्रॉस-डिव्हाइस सेवा"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"तुमच्या डिव्हाइसदरम्यान ॲप्स स्ट्रीम करण्यासाठी <xliff:g id="APP_NAME">%1$s</xliff:g> हे तुमच्या <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> च्या वतीने परवानगीची विनंती करत आहे"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;यामध्ये &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;strong&gt; वरील मायक्रोफोन, कॅमेरा आणि स्थान अ‍ॅक्सेस व इतर संवेदनशील परवानग्यांचा समावेश असू शकतो &lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;तुम्ही &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt; वर तुमच्या सेटिंग्ज मध्ये या परवानग्या कधीही बदलू शकता&lt;/strong&gt;.&lt;/p&gt;"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"अ‍ॅप आयकन"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"अधिक माहिती बटण"</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"फोन"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"एसएमएस"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"Contacts"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"Calendar"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"जवळपासची डिव्हाइस"</string>
<string name="permission_storage" msgid="6831099350839392343">"फोटो आणि मीडिया"</string>
<string name="permission_notification" msgid="693762568127741203">"सूचना"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"ॲप्स"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"तुमचा फोन नंबर आणि नेटवर्कची माहिती अ‍ॅक्सेस करू शकते. कॉल करणे व VoIP, व्हॉइसमेल, कॉल रीडिरेक्ट करणे आणि कॉल लॉग संपादित करणे यांसाठी आवश्यक"</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"आमची संपर्क सूची रीड, तयार आणि संपादित करू शकते, तसेच तुमच्या डिव्हाइसवर वापरल्या जाणाऱ्या खात्यांची सूची अ‍ॅक्सेस करू शकते"</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"संपर्क, मेसेज आणि फोटो यांसारख्या माहितीचा समावेश असलेल्या सर्व सूचना वाचू शकते"</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"तुमच्या फोनवरील ॲप्स स्ट्रीम करा"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-ms/strings.xml b/packages/CompanionDeviceManager/res/values-ms/strings.xml
index a2a8e2ad21a5..ac9acbc4f132 100644
--- a/packages/CompanionDeviceManager/res/values-ms/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ms/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"Benarkan &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; mengakses &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; anda"</string>
<string name="profile_name_watch" msgid="576290739483672360">"jam tangan"</string>
<string name="chooser_title" msgid="2262294130493605839">"Pilih <xliff:g id="PROFILE_NAME">%1$s</xliff:g> untuk diurus oleh &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"Apl ini diperlukan untuk mengurus <xliff:g id="DEVICE_NAME">%1$s</xliff:g> anda. <xliff:g id="APP_NAME">%2$s</xliff:g> akan dibenarkan berinteraksi dengan pemberitahuan anda dan mengakses kebenaran Telefon, SMS, Kenalan, Kalendar, Log panggilan serta Peranti berdekatan anda."</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"Apl ini diperlukan untuk mengurus <xliff:g id="DEVICE_NAME">%1$s</xliff:g> anda. <xliff:g id="APP_NAME">%2$s</xliff:g> akan dibenarkan berinteraksi dengan kebenaran ini:"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"Benarkan &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; mengakses maklumat ini daripada telefon anda"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"Perkhidmatan silang peranti"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> sedang meminta kebenaran bagi pihak <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> anda untuk menstrim apl antara peranti anda"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Ini mungkin termasuk akses Mikrofon, Kamera dan Lokasi serta kebenaran sensitif lain pada &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;Anda boleh menukar kebenaran ini pada bila-bila masa dalam Tetapan anda pada &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"Ikon Apl"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"Butang Maklumat Lagi"</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"Telefon"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"Kenalan"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"Kalendar"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"Peranti berdekatan"</string>
<string name="permission_storage" msgid="6831099350839392343">"Foto dan media"</string>
<string name="permission_notification" msgid="693762568127741203">"Pemberitahuan"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"Apl"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"Boleh mengakses nombor telefon dan maklumat rangkaian anda. Diperlukan untuk membuat panggilan dan VoIP, mel suara, ubah hala panggilan dan mengedit log panggilan"</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"Boleh membaca, membuat atau mengedit senarai kenalan kami, serta mengakses senarai semua akaun yang digunakan pada peranti anda"</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"Boleh membaca semua pemberitahuan, termasuk maklumat seperti kenalan, mesej dan foto"</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"Strim apl telefon anda"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-my/strings.xml b/packages/CompanionDeviceManager/res/values-my/strings.xml
index 87dc08a427a9..271ddeeee1c6 100644
--- a/packages/CompanionDeviceManager/res/values-my/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-my/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"သင်၏ &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; ကို သုံးရန် &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ကို ခွင့်ပြုပါ"</string>
<string name="profile_name_watch" msgid="576290739483672360">"နာရီ"</string>
<string name="chooser_title" msgid="2262294130493605839">"&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; က စီမံခန့်ခွဲရန် <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ကို ရွေးချယ်ပါ"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"သင်၏ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ကို စီမံခန့်ခွဲရန် ဤအက်ပ်လိုအပ်သည်။ သင်၏ဖုန်း၊ SMS စာတိုစနစ်၊ အဆက်အသွယ်များ၊ ပြက္ခဒိန်၊ ခေါ်ဆိုမှတ်တမ်းနှင့် အနီးတစ်ဝိုက်ရှိ စက်များဆိုင်ရာ ခွင့်ပြုချက်များသုံးရန်၊ အကြောင်းကြားချက်များနှင့် ပြန်လှန်တုံ့ပြန်ရန် <xliff:g id="APP_NAME">%2$s</xliff:g> ကို ခွင့်ပြုမည်။"</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"သင်၏ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ကို စီမံခန့်ခွဲရန် ဤအက်ပ်လိုအပ်သည်။ ဤခွင့်ပြုချက်များနှင့် ပြန်လှန်တုံ့ပြန်ရန် <xliff:g id="APP_NAME">%2$s</xliff:g> ကို ခွင့်ပြုမည်-"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ကို သင့်ဖုန်းမှ ဤအချက်အလက် သုံးခွင့်ပြုမည်"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"စက်များကြားသုံး ဝန်ဆောင်မှုများ"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> သည် သင်၏စက်များအကြား အက်ပ်များတိုက်ရိုက်လွှင့်ရန် <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> ကိုယ်စား ခွင့်ပြုချက်တောင်းနေသည်"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;၎င်းတွင် မိုက်ခရိုဖုန်း၊ ကင်မရာ၊ တည်နေရာ အသုံးပြုခွင့်အပြင် &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;ပေါ်ရှိ အခြား သတိထားရမည့် ခွင့်ပြုချက်များ ပါဝင်နိုင်သည်။&lt;/p&gt; &lt;p&gt;ဤခွင့်ပြုချက်များကို &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;ပေါ်ရှိ သင်၏ဆက်တင်များတွင် အချိန်မရွေးပြောင်းနိုင်သည်။&lt;/p&gt;"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"အက်ပ်သင်္ကေတ"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"နောက်ထပ်အချက်အလက်များ ခလုတ်"</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"ဖုန်း"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"SMS စာတိုစနစ်"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"အဆက်အသွယ်များ"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"ပြက္ခဒိန်"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"အနီးတစ်ဝိုက်ရှိ စက်များ"</string>
<string name="permission_storage" msgid="6831099350839392343">"ဓာတ်ပုံနှင့် မီဒီယာများ"</string>
<string name="permission_notification" msgid="693762568127741203">"အကြောင်းကြားချက်များ"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"အက်ပ်များ"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"သင့်ဖုန်းနံပါတ်နှင့် ကွန်ရက်အချက်အလက်ကို သုံးနိုင်သည်။ ဖုန်းခေါ်ဆိုခြင်းနှင့် VoIP၊ အသံမေးလ်၊ ခေါ်ဆိုမှု တစ်ဆင့်ပြန်လွှဲခြင်းနှင့် ခေါ်ဆိုမှတ်တမ်းများ တည်းဖြတ်ခြင်းတို့အတွက် လိုအပ်သည်"</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"အဆက်အသွယ် စာရင်းကို ဖတ်နိုင်၊ ပြုလုပ်နိုင် (သို့) တည်းဖြတ်နိုင်သည့်ပြင် သင့်စက်တွင်သုံးသော အကောင့်အားလုံး၏စာရင်းကို သုံးနိုင်သည်"</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"အဆက်အသွယ်၊ မက်ဆေ့ဂျ်နှင့် ဓာတ်ပုံကဲ့သို့ အချက်အလက်များအပါအဝင် အကြောင်းကြားချက်အားလုံးကို ဖတ်နိုင်သည်"</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"သင့်ဖုန်းရှိအက်ပ်များကို တိုက်ရိုက်ဖွင့်နိုင်သည်"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-nb/strings.xml b/packages/CompanionDeviceManager/res/values-nb/strings.xml
index 82a0282219ab..01f4d32fbc6b 100644
--- a/packages/CompanionDeviceManager/res/values-nb/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-nb/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"Tillat at &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; bruker &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
<string name="profile_name_watch" msgid="576290739483672360">"klokke"</string>
<string name="chooser_title" msgid="2262294130493605839">"Velg <xliff:g id="PROFILE_NAME">%1$s</xliff:g> som skal administreres av &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"Appen kreves for å administrere <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> får tillatelse til å samhandle med varslene dine og har tilgang til tillatelsene for telefon, SMS, kontakter, kalender, samtalelogger og enheter i nærheten."</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"Appen kreves for å administrere <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> får tillatelse til å samhandle med disse tillatelsene:"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"Gi &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; tilgang til denne informasjonen fra telefonen din"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"Tjenester på flere enheter"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> ber om tillatelse til å strømme apper mellom enhetene dine, på vegne av <xliff:g id="DEVICE_TYPE">%2$s</xliff:g>"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Dette kan inkludere tilgang til mikrofon, kamera og posisjon samt andre sensitive tillatelser på &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;Du kan når som helst endre disse tillatelsene i innstillingene på &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"Appikon"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"Mer informasjon-knapp"</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"Telefon"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"Kontakter"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"Kalender"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"Enheter i nærheten"</string>
<string name="permission_storage" msgid="6831099350839392343">"Bilder og medier"</string>
<string name="permission_notification" msgid="693762568127741203">"Varsler"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"Apper"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"Kan se telefonnummeret ditt og nettverksinformasjon. Dette kreves for samtaler og VoIP, talepost, viderekobling av anrop og endring av samtalelogger"</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"Kan lese, opprette eller endre kontaktlisten din og se listen over alle kontoene som brukes på enheten"</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"Kan lese alle varsler, inkludert informasjon som kontakter, meldinger og bilder"</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"Strøm appene på telefonen"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-nl/strings.xml b/packages/CompanionDeviceManager/res/values-nl/strings.xml
index ed8890b96f38..28366f64f3a1 100644
--- a/packages/CompanionDeviceManager/res/values-nl/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-nl/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; toegang geven tot je &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
<string name="profile_name_watch" msgid="576290739483672360">"horloge"</string>
<string name="chooser_title" msgid="2262294130493605839">"Een <xliff:g id="PROFILE_NAME">%1$s</xliff:g> kiezen om te beheren met &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"De app is vereist om je <xliff:g id="DEVICE_NAME">%1$s</xliff:g> te beheren. <xliff:g id="APP_NAME">%2$s</xliff:g> kan interactie hebben met je meldingen en toegang krijgen tot rechten voor Telefoon, Sms, Contacten, Agenda, Gesprekslijsten en Apparaten in de buurt."</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"De app is vereist om je <xliff:g id="DEVICE_NAME">%1$s</xliff:g> te beheren. <xliff:g id="APP_NAME">%2$s</xliff:g> heeft toestemming om interactie te hebben met de volgende rechten:"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; toegang geven tot deze informatie op je telefoon"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"Cross-device-services"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> vraagt namens jouw <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> toestemming om apps te streamen tussen je apparaten"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Dit kan toegang tot de microfoon, camera en je locatie en andere gevoelige rechten op je &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt; omvatten.&lt;/p&gt; &lt;p&gt;Je kunt deze rechten op elk moment wijzigen in je Instellingen op de <xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>.&lt;/p&gt;"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"App-icoon"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"Knop Meer informatie"</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"Telefoon"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"Sms"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"Contacten"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"Agenda"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"Apparaten in de buurt"</string>
<string name="permission_storage" msgid="6831099350839392343">"Foto\'s en media"</string>
<string name="permission_notification" msgid="693762568127741203">"Meldingen"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"Apps"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"Heeft toegang tot je telefoonnummer en netwerkgegevens. Vereist voor bellen en VoIP, voicemail, gesprek omleiden en gesprekslijsten bewerken."</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"Kan je contactenlijst lezen, maken of bewerken, en heeft toegang tot de contactenlijst voor alle accounts die op je apparaat worden gebruikt."</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"Kan alle meldingen lezen, waaronder informatie zoals contacten, berichten en foto\'s"</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"Stream de apps van je telefoon"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-or/strings.xml b/packages/CompanionDeviceManager/res/values-or/strings.xml
index 225074c97450..5f7f52efbf14 100644
--- a/packages/CompanionDeviceManager/res/values-or/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-or/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"ଆପଣଙ୍କ &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;କୁ ଆକ୍ସେସ କରିବା ପାଇଁ &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;କୁ ଅନୁମତି ଦିଅନ୍ତୁ"</string>
<string name="profile_name_watch" msgid="576290739483672360">"ୱାଚ୍"</string>
<string name="chooser_title" msgid="2262294130493605839">"&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; ଦ୍ୱାରା ପରିଚାଳିତ ହେବା ପାଇଁ ଏକ <xliff:g id="PROFILE_NAME">%1$s</xliff:g>କୁ ବାଛନ୍ତୁ"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"ଆପଣଙ୍କ <xliff:g id="DEVICE_NAME">%1$s</xliff:g>କୁ ପରିଚାଳନା କରିବା ପାଇଁ ଆପ ଆବଶ୍ୟକ। ଆପଣଙ୍କ ବିଜ୍ଞପ୍ତିଗୁଡ଼ିକ ସହ ଇଣ୍ଟରାକ୍ଟ କରିବା ଏବଂ ଆପଣଙ୍କର ଫୋନ, SMS, କଣ୍ଟାକ୍ଟ, କେଲେଣ୍ଡର, କଲ ଲଗ ଓ ଆଖପାଖର ଡିଭାଇସ ଅନୁମତିଗୁଡ଼ିକୁ ଆକ୍ସେସ କରିବା ପାଇଁ <xliff:g id="APP_NAME">%2$s</xliff:g>କୁ ଅନୁମତି ଦିଆଯିବ।"</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"ଆପଣଙ୍କ <xliff:g id="DEVICE_NAME">%1$s</xliff:g>କୁ ପରିଚାଳନା କରିବା ପାଇଁ ଆପ ଆବଶ୍ୟକ। ଏହି ଅନୁମତିଗୁଡ଼ିକ ସହ ଇଣ୍ଟରାକ୍ଟ କରିବା ପାଇଁ <xliff:g id="APP_NAME">%2$s</xliff:g>କୁ ଅନୁମତି ଦିଆଯିବ:"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"ଆପଣଙ୍କ ଫୋନରୁ ଏହି ସୂଚନାକୁ ଆକ୍ସେସ କରିବା ପାଇଁ &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;କୁ ଅନୁମତି ଦିଅନ୍ତୁ"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"କ୍ରସ-ଡିଭାଇସ ସେବାଗୁଡ଼ିକ"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"ଆପଣଙ୍କ ଡିଭାଇସଗୁଡ଼ିକ ମଧ୍ୟରେ ଆପ୍ସକୁ ଷ୍ଟ୍ରିମ କରିବା ପାଇଁ <xliff:g id="APP_NAME">%1$s</xliff:g> ଆପଣଙ୍କର <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> ତରଫରୁ ଅନୁମତି ପାଇଁ ଅନୁରୋଧ କରୁଛି"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;ଏହା &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;ରେ ମାଇକ୍ରୋଫୋନ, କ୍ୟାମେରା ଏବଂ ଲୋକେସନ ଆକ୍ସେସ ଓ ଅନ୍ୟ ସମ୍ବେଦନଶୀଳ ଅନୁମତିଗୁଡ଼ିକୁ ଅନ୍ତର୍ଭୁକ୍ତ କରିପାରେ।&lt;/p&gt; &lt;p&gt;ଆପଣ &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;ରେ ଯେ କୌଣସି ସମୟରେ ଆପଣଙ୍କ ସେଟିଂସରେ ଏହି ଅନୁମତିଗୁଡ଼ିକୁ ପରିବର୍ତ୍ତନ କରିପାରିବେ।&lt;/p&gt;"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"ଆପ ଆଇକନ"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"ଅଧିକ ସୂଚନା ବଟନ"</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"ଫୋନ"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"କଣ୍ଟାକ୍ଟଗୁଡ଼ିକ"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"କେଲେଣ୍ଡର"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"ଆଖପାଖର ଡିଭାଇସଗୁଡ଼ିକ"</string>
<string name="permission_storage" msgid="6831099350839392343">"ଫଟୋ ଏବଂ ମିଡିଆ"</string>
<string name="permission_notification" msgid="693762568127741203">"ବିଜ୍ଞପ୍ତିଗୁଡ଼ିକ"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"ଆପ୍ସ"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"ଆପଣଙ୍କ ଫୋନ ନମ୍ବର ଏବଂ ନେଟୱାର୍କ ସୂଚନା ଆକ୍ସେସ କରିପାରିବେ।କଲ କରିବା ଓ VoIP, ଭଏସମେଲ, କଲ ଡାଇରେକ୍ଟ ଏବଂ କଲ ଲଗକୁ ଏଡିଟ କରିବା ପାଇଁ ଆବଶ୍ୟକ"</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"ଆପଣଙ୍କ କଣ୍ଟାକ୍ଟ ତାଲିକା ପଢ଼ିପାରିବେ, ତିଆରି କିମ୍ବା ଏଡିଟ କରିପାରିବେ ତଥା ଆପଣଙ୍କ ଡିଭାଇସରେ ବ୍ୟବହାର କରାଯାଇଥିବା ସମସ୍ତ ଆକାଉଣ୍ଟର ତାଲିକାକୁ ଆକ୍ସେସ କରିପାରିବେ"</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"ଯୋଗାଯୋଗ, ମେସେଜ ଏବଂ ଫଟୋଗୁଡ଼ିକ ପରି ସୂଚନା ସମେତ ସମସ୍ତ ବିଜ୍ଞପ୍ତିକୁ ପଢ଼ିପାରିବ"</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"ଆପଣଙ୍କ ଫୋନର ଆପ୍ସକୁ ଷ୍ଟ୍ରିମ କରନ୍ତୁ"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-pa/strings.xml b/packages/CompanionDeviceManager/res/values-pa/strings.xml
index 00fbc3c7c380..4d9b8d3ce4a4 100644
--- a/packages/CompanionDeviceManager/res/values-pa/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-pa/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ਨੂੰ ਤੁਹਾਡੇ &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; ਤੱਕ ਪਹੁੰਚ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿਓ"</string>
<string name="profile_name_watch" msgid="576290739483672360">"ਸਮਾਰਟ-ਵਾਚ"</string>
<string name="chooser_title" msgid="2262294130493605839">"&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; ਵੱਲੋਂ ਪ੍ਰਬੰਧਿਤ ਕੀਤੇ ਜਾਣ ਲਈ <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ਚੁਣੋ"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"ਇਹ ਐਪ ਤੁਹਾਡੇ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ਦਾ ਪ੍ਰਬੰਧਨ ਕਰਨ ਲਈ ਲੋੜੀਂਦੀ ਹੈ। <xliff:g id="APP_NAME">%2$s</xliff:g> ਨੂੰ ਤੁਹਾਡੀਆਂ ਸੂਚਨਾਵਾਂ ਨਾਲ ਅੰਤਰਕਿਰਿਆ ਕਰਨ ਅਤੇ ਤੁਹਾਡੇ ਫ਼ੋਨ, SMS, ਸੰਪਰਕਾਂ, ਕੈਲੰਡਰ, ਕਾਲ ਲੌਗਾਂ ਅਤੇ ਨਜ਼ਦੀਕੀ ਡੀਵਾਈਸਾਂ ਸੰਬੰਧੀ ਇਜਾਜ਼ਤਾਂ ਤੱਕ ਪਹੁੰਚ ਕਰਨ ਦੀ ਆਗਿਆ ਹੋਵੇਗੀ।"</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"ਇਹ ਐਪ ਤੁਹਾਡੇ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ਦਾ ਪ੍ਰਬੰਧਨ ਕਰਨ ਲਈ ਲੋੜੀਂਦੀ ਹੈ। <xliff:g id="APP_NAME">%2$s</xliff:g> ਨੂੰ ਇਨ੍ਹਾਂ ਇਜਾਜ਼ਤਾਂ ਨਾਲ ਅੰਤਰਕਿਰਿਆ ਕਰਨ ਦੀ ਆਗਿਆ ਹੋਵੇਗੀ:"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ਨੂੰ ਤੁਹਾਡੇ ਫ਼ੋਨ ਤੋਂ ਇਸ ਜਾਣਕਾਰੀ ਤੱਕ ਪਹੁੰਚ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿਓ"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"ਕ੍ਰਾਸ-ਡੀਵਾਈਸ ਸੇਵਾਵਾਂ"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> ਤੁਹਾਡੇ <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> ਦੀ ਤਰਫ਼ੋਂ ਤੁਹਾਡੇ ਡੀਵਾਈਸਾਂ ਵਿਚਕਾਰ ਐਪਾਂ ਨੂੰ ਸਟ੍ਰੀਮ ਕਰਨ ਦੀ ਇਜਾਜ਼ਤ ਮੰਗ ਰਹੀ ਹੈ"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;ਇਸ ਵਿੱਚ ਮਾਈਕ੍ਰੋਫ਼ੋਨ, ਕੈਮਰਾ, ਟਿਕਾਣਾ ਪਹੁੰਚ ਅਤੇ &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt; \'ਤੇ ਮੌਜੂਦ ਹੋਰ ਸੰਵੇਦਨਸ਼ੀਲ ਜਾਣਕਾਰੀ ਤੱਕ ਪਹੁੰਚ ਸੰਬੰਧੀ ਇਜਾਜ਼ਤਾਂ ਸ਼ਾਮਲ ਹੋ ਸਕਦੀਆਂ ਹਨ।&lt;/p&gt; &lt;p&gt;ਤੁਸੀਂ &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt; \'ਤੇ ਮੌਜੂਦ ਆਪਣੀਆਂ ਸੈਟਿੰਗਾਂ ਵਿੱਚ ਜਾ ਕੇ ਕਦੇ ਵੀ ਇਨ੍ਹਾਂ ਇਜਾਜ਼ਤਾਂ ਨੂੰ ਬਦਲ ਸਕਦੇ ਹੋ।&lt;/p&gt;"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"ਐਪ ਪ੍ਰਤੀਕ"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"ਹੋਰ ਜਾਣਕਾਰੀ ਬਟਨ"</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"ਫ਼ੋਨ"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"ਸੰਪਰਕ"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"ਕੈਲੰਡਰ"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"ਨਜ਼ਦੀਕੀ ਡੀਵਾਈਸ"</string>
<string name="permission_storage" msgid="6831099350839392343">"ਫ਼ੋਟੋਆਂ ਅਤੇ ਮੀਡੀਆ"</string>
<string name="permission_notification" msgid="693762568127741203">"ਸੂਚਨਾਵਾਂ"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"ਐਪਾਂ"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"ਤੁਹਾਡੇ ਫ਼ੋਨ ਨੰਬਰ ਅਤੇ ਨੈੱਟਵਰਕ ਜਾਣਕਾਰੀ ਤੱਕ ਪਹੁੰਚ ਕਰ ਸਕਦੀ ਹੈ। ਇਹ ਕਾਲਾਂ ਅਤੇ VoIP, ਵੌਇਸਮੇਲ, ਕਾਲ ਨੂੰ ਰੀਡਾਇਰੈਕਟ ਕਰਨ ਅਤੇ ਕਾਲ ਲੌਗਾਂ ਦਾ ਸੰਪਾਦਨ ਕਰਨ ਲਈ ਲੋੜੀਂਦੀ ਹੈ"</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"ਤੁਹਾਡੀ ਸੰਪਰਕ ਸੂਚੀ ਨੂੰ ਪੜ੍ਹ, ਬਣਾ ਸਕਦੀ ਹੈ ਜਾਂ ਉਸਦਾ ਸੰਪਾਦਨ ਕਰ ਸਕਦੀ ਹੈ ਅਤੇ ਇਸ ਤੋਂ ਇਲਾਵਾ ਤੁਹਾਡੇ ਡੀਵਾਈਸ \'ਤੇ ਵਰਤੇ ਗਏ ਸਾਰੇ ਖਾਤਿਆਂ ਦੀ ਸੂਚੀ ਤੱਕ ਪਹੁੰਚ ਕਰ ਸਕਦੀ ਹੈ"</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"ਤੁਸੀਂ ਸਾਰੀਆਂ ਸੂਚਨਾਵਾਂ ਪੜ੍ਹ ਸਕਦੇ ਹੋ, ਜਿਨ੍ਹਾਂ ਵਿੱਚ ਸੰਪਰਕਾਂ, ਸੁਨੇਹਿਆਂ ਅਤੇ ਫ਼ੋਟੋਆਂ ਵਰਗੀ ਜਾਣਕਾਰੀ ਸ਼ਾਮਲ ਹੁੰਦੀ ਹੈ"</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"ਆਪਣੇ ਫ਼ੋਨ ਦੀਆਂ ਐਪਾਂ ਨੂੰ ਸਟ੍ਰੀਮ ਕਰੋ"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-pl/strings.xml b/packages/CompanionDeviceManager/res/values-pl/strings.xml
index f312507ae659..2c7a038b97ea 100644
--- a/packages/CompanionDeviceManager/res/values-pl/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-pl/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"Zezwól na dostęp aplikacji &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; do urządzenia &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
<string name="profile_name_watch" msgid="576290739483672360">"zegarek"</string>
<string name="chooser_title" msgid="2262294130493605839">"Wybierz profil <xliff:g id="PROFILE_NAME">%1$s</xliff:g>, którym ma zarządzać aplikacja &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"Aplikacja jest niezbędna do zarządzania profilem <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. Aplikacja <xliff:g id="APP_NAME">%2$s</xliff:g> będzie mogła korzystać z powiadomień oraz uprawnień dotyczących telefonu, SMS-ów, kontaktów, kalendarza, rejestrów połączeń i Urządzeń w pobliżu."</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"Aplikacja jest niezbędna do zarządzania profilem <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. Aplikacja <xliff:g id="APP_NAME">%2$s</xliff:g> będzie mogła korzystać z tych powiadomień:"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"Zezwól urządzeniu &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; na dostęp do tych informacji na Twoim telefonie"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"Usługi na innym urządzeniu"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"Aplikacja <xliff:g id="APP_NAME">%1$s</xliff:g> prosi w imieniu urządzenia <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> o uprawnienia dotyczące strumieniowego odtwarzania treści z aplikacji na innym urządzeniu"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Mogą one obejmować dane dostęp do Mikrofonu, Aparatu i lokalizacji oraz inne uprawnienia newralgiczne na urządzeniu &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;Możesz w dowolnym momencie zmienić uprawnienia na urządzeniu &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"Ikona aplikacji"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"Przycisk – więcej informacji"</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"Telefon"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"SMS-y"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"Kontakty"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"Kalendarz"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"Urządzenia w pobliżu"</string>
<string name="permission_storage" msgid="6831099350839392343">"Zdjęcia i multimedia"</string>
<string name="permission_notification" msgid="693762568127741203">"Powiadomienia"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"Aplikacje"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"Może korzystać z numeru telefonu i informacji o sieci. Wymagane przy nawiązywaniu połączeń, korzystaniu z VoIP oraz poczty głosowej, przekierowywaniu połączeń oraz edytowaniu rejestrów połączeń"</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"Może odczytywać, tworzyć i edytować listę kontaktów, jak również korzystać z listy wszystkich kont używanych na urządzeniu"</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"Może odczytywać wszystkie powiadomienia, w tym informacje takie jak kontakty, wiadomości i zdjęcia"</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"Odtwarzaj strumieniowo aplikacje z telefonu"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-pt-rBR/strings.xml b/packages/CompanionDeviceManager/res/values-pt-rBR/strings.xml
index d1b8774149e8..1a0d4d98a7b3 100644
--- a/packages/CompanionDeviceManager/res/values-pt-rBR/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-pt-rBR/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"Permitir que o app &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; acesse o dispositivo &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
<string name="profile_name_watch" msgid="576290739483672360">"relógio"</string>
<string name="chooser_title" msgid="2262294130493605839">"Escolha um <xliff:g id="PROFILE_NAME">%1$s</xliff:g> para ser gerenciado pelo app &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"O app é necessário para gerenciar seu <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. O <xliff:g id="APP_NAME">%2$s</xliff:g> vai poder interagir com suas notificações e acessar os apps Telefone, SMS, Contatos, Google Agenda, registros de chamadas e as permissões de dispositivos por perto."</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"O app é necessário para gerenciar seu <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> vai poder interagir com estas permissões:"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"Permitir que o app &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; acesse estas informações do smartphone"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"Serviços entre dispositivos"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"O app <xliff:g id="APP_NAME">%1$s</xliff:g> está pedindo permissão em nome do seu <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> para fazer streaming de apps entre seus dispositivos"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Isso pode incluir acesso a microfone, câmera e localização e outras permissões sensíveis no &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;Você pode mudar essas permissões a qualquer momento nas configurações do &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"Ícone do app"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"Botão \"Mais informações\""</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"Smartphone"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"Contatos"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"Agenda"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"Dispositivos por perto"</string>
<string name="permission_storage" msgid="6831099350839392343">"Fotos e mídia"</string>
<string name="permission_notification" msgid="693762568127741203">"Notificações"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"Apps"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"Acessar seu número de telefone e informações da rede. Necessária para chamadas e VoIP, correio de voz, redirecionamento de chamadas e edição de registros de chamadas"</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"Ler, criar ou editar sua lista de contatos, e também acessar a lista de contatos de todas as contas usadas no seu dispositivo"</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"Pode ler todas as notificações, incluindo informações como contatos, mensagens e fotos"</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"Fazer transmissão dos apps no seu smartphone"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-pt-rPT/strings.xml b/packages/CompanionDeviceManager/res/values-pt-rPT/strings.xml
index a11f7ff22b52..5f3eeeb4d872 100644
--- a/packages/CompanionDeviceManager/res/values-pt-rPT/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-pt-rPT/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"Permita que a app &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; aceda ao seu &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
<string name="profile_name_watch" msgid="576290739483672360">"relógio"</string>
<string name="chooser_title" msgid="2262294130493605839">"Escolha um <xliff:g id="PROFILE_NAME">%1$s</xliff:g> para ser gerido pela app &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"A app é necessária para gerir o seu <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. A app <xliff:g id="APP_NAME">%2$s</xliff:g> vai poder interagir com as suas notificações e aceder às autorizações do Telemóvel, SMS, Contactos, Calendário, Registos de chamadas e Dispositivos próximos."</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"A app é necessária para gerir o seu <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. A app <xliff:g id="APP_NAME">%2$s</xliff:g> vai poder interagir com estas autorizações:"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"Permita que a app &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; aceda a estas informações do seu telemóvel"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"Serviços entre dispositivos"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"A app <xliff:g id="APP_NAME">%1$s</xliff:g> está a pedir autorização em nome do seu dispositivo <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> para fazer stream de apps entre os seus dispositivos"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Isto pode incluir o acesso ao microfone, câmara e localização, bem como a outras autorizações confidenciais no dispositivo &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;Pode alterar estas autorizações em qualquer altura nas Definições do dispositivo &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"Ícone da app"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"Botão Mais informações"</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"Telemóvel"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"Contactos"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"Calendário"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"Dispositivos próximos"</string>
<string name="permission_storage" msgid="6831099350839392343">"Fotos e multimédia"</string>
<string name="permission_notification" msgid="693762568127741203">"Notificações"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"Apps"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"Pode aceder ao seu número de telefone e informações da rede. É precisa para fazer chamadas e VoIP (voice over Internet Protocol), o correio de voz, redirecionar a chamada e editar registos de chamadas"</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"Pode ler, criar ou editar a nossa lista de contactos e aceder à lista de todas as contas usadas no seu dispositivo"</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"Pode ler todas as notificações, incluindo informações como contactos, mensagens e fotos"</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"Faça stream das apps do telemóvel"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-pt/strings.xml b/packages/CompanionDeviceManager/res/values-pt/strings.xml
index d1b8774149e8..1a0d4d98a7b3 100644
--- a/packages/CompanionDeviceManager/res/values-pt/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-pt/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"Permitir que o app &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; acesse o dispositivo &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
<string name="profile_name_watch" msgid="576290739483672360">"relógio"</string>
<string name="chooser_title" msgid="2262294130493605839">"Escolha um <xliff:g id="PROFILE_NAME">%1$s</xliff:g> para ser gerenciado pelo app &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"O app é necessário para gerenciar seu <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. O <xliff:g id="APP_NAME">%2$s</xliff:g> vai poder interagir com suas notificações e acessar os apps Telefone, SMS, Contatos, Google Agenda, registros de chamadas e as permissões de dispositivos por perto."</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"O app é necessário para gerenciar seu <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> vai poder interagir com estas permissões:"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"Permitir que o app &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; acesse estas informações do smartphone"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"Serviços entre dispositivos"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"O app <xliff:g id="APP_NAME">%1$s</xliff:g> está pedindo permissão em nome do seu <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> para fazer streaming de apps entre seus dispositivos"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Isso pode incluir acesso a microfone, câmera e localização e outras permissões sensíveis no &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;Você pode mudar essas permissões a qualquer momento nas configurações do &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"Ícone do app"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"Botão \"Mais informações\""</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"Smartphone"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"Contatos"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"Agenda"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"Dispositivos por perto"</string>
<string name="permission_storage" msgid="6831099350839392343">"Fotos e mídia"</string>
<string name="permission_notification" msgid="693762568127741203">"Notificações"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"Apps"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"Acessar seu número de telefone e informações da rede. Necessária para chamadas e VoIP, correio de voz, redirecionamento de chamadas e edição de registros de chamadas"</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"Ler, criar ou editar sua lista de contatos, e também acessar a lista de contatos de todas as contas usadas no seu dispositivo"</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"Pode ler todas as notificações, incluindo informações como contatos, mensagens e fotos"</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"Fazer transmissão dos apps no seu smartphone"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-ro/strings.xml b/packages/CompanionDeviceManager/res/values-ro/strings.xml
index 7c33ab3d61fd..35c0888a703b 100644
--- a/packages/CompanionDeviceManager/res/values-ro/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ro/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"Permite ca &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; să acceseze dispozitivul &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
<string name="profile_name_watch" msgid="576290739483672360">"ceas"</string>
<string name="chooser_title" msgid="2262294130493605839">"Alege un profil <xliff:g id="PROFILE_NAME">%1$s</xliff:g> pe care să îl gestioneze &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"Aplicația este necesară pentru a gestiona <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> va putea să interacționeze cu notificările tale și să îți acceseze permisiunile pentru Telefon, SMS, Agendă, Calendar, Jurnale de apeluri și Dispozitive din apropiere."</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"Aplicația este necesară pentru a gestiona <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> va putea să interacționeze cu următoarele permisiuni:"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"Permite ca &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; să acceseze aceste informații de pe telefon"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"Servicii pe mai multe dispozitive"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> solicită permisiunea pentru <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> de a reda în stream aplicații între dispozitivele tale"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Aici pot fi incluse accesul la microfon, la camera foto, la locație și alte permisiuni de accesare a informațiilor sensibile de pe &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;Poți modifica oricând aceste permisiuni din Setările de pe &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"Pictograma aplicației"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"Butonul Mai multe informații"</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"Telefon"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"Agendă"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"Calendar"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"Dispozitive din apropiere"</string>
<string name="permission_storage" msgid="6831099350839392343">"Fotografii și media"</string>
<string name="permission_notification" msgid="693762568127741203">"Notificări"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"Aplicații"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"Poate să acceseze numărul tău de telefon și informațiile despre rețea. Permisiunea este necesară pentru inițierea apelurilor și VoIP, mesaje vocale, redirecționarea apelurilor și editarea jurnalelor de apeluri"</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"Poate să citească, să creeze sau să editeze agenda, precum și să acceseze lista tuturor conturilor folosite pe dispozitiv"</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"Poate să citească toate notificările, inclusiv informații cum ar fi agenda, mesajele și fotografiile"</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"Să redea în stream aplicațiile telefonului"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-ru/strings.xml b/packages/CompanionDeviceManager/res/values-ru/strings.xml
index d82fa76a2c4b..612601aefdf7 100644
--- a/packages/CompanionDeviceManager/res/values-ru/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ru/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"Разрешите приложению &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; доступ к устройству &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
<string name="profile_name_watch" msgid="576290739483672360">"часы"</string>
<string name="chooser_title" msgid="2262294130493605839">"Выберите устройство (<xliff:g id="PROFILE_NAME">%1$s</xliff:g>), которым будет управлять приложение &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"Это приложение необходимо для управления устройством \"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>\". Приложение \"<xliff:g id="APP_NAME">%2$s</xliff:g>\" получит доступ к уведомлениям, а также следующие разрешения: телефон, SMS, контакты, календарь, список вызовов и устройства поблизости."</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"Это приложение необходимо для управления устройством \"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>\". Приложение \"<xliff:g id="APP_NAME">%2$s</xliff:g>\" получит следующие разрешения:"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"Разрешите приложению &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; получать эту информацию с вашего телефона"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"Сервисы стриминга приложений"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"Приложение \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" запрашивает разрешение от имени вашего устройства <xliff:g id="DEVICE_TYPE">%2$s</xliff:g>, чтобы транслировать приложения между вашими устройствами."</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Сюда может входить доступ к микрофону, камере и данным о местоположении, а также другие разрешения на доступ к конфиденциальной информации на устройстве &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;Вы можете в любое время изменить разрешения в настройках устройства &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"Значок приложения"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"Кнопка информации"</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"Телефон"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"Контакты"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"Календарь"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"Устройства поблизости"</string>
<string name="permission_storage" msgid="6831099350839392343">"Фотографии и медиафайлы"</string>
<string name="permission_notification" msgid="693762568127741203">"Уведомления"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"Приложения"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"Доступ к номеру телефона и информации о сети. Это разрешение необходимо, чтобы совершать обычные и VoIP-звонки, отправлять голосовые сообщения, перенаправлять вызовы и редактировать списки вызовов."</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"Возможность читать, создавать и редактировать список контактов, а также получать доступ к списку всех аккаунтов на вашем устройстве."</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"Чтение всех уведомлений, в том числе сведений о контактах, сообщениях и фотографиях."</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"Трансляция приложений с телефона."</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-si/strings.xml b/packages/CompanionDeviceManager/res/values-si/strings.xml
index f58f042380a9..0743dbafe77a 100644
--- a/packages/CompanionDeviceManager/res/values-si/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-si/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; හට ඔබගේ &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; කළමනාකරණය කිරීමට ඉඩ දෙන්න"</string>
<string name="profile_name_watch" msgid="576290739483672360">"ඔරලෝසුව"</string>
<string name="chooser_title" msgid="2262294130493605839">"&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; මගින් කළමනාකරණය කරනු ලැබීමට <xliff:g id="PROFILE_NAME">%1$s</xliff:g>ක් තෝරන්න"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"ඔබේ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> කළමනා කිරීමට මෙම යෙදුම අවශ්‍යයි. <xliff:g id="APP_NAME">%2$s</xliff:g> ඔබේ දැනුම්දීම් සමග අන්තර්ක්‍රියා කිරීමට සහ ඔබේ දුරකථනය, කෙටිපණිවුඩය, සම්බන්‍ධතා, දිනදර්ශනය, ඇමතුම් ලොග සහ අවට උපාංග අවසර වෙත ප්‍රවේශ වීමට ඉඩ දෙයි."</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"ඔබේ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> කළමනා කිරීමට මෙම යෙදුම අවශ්‍යයි. <xliff:g id="APP_NAME">%2$s</xliff:g> හට මෙම අවසර සමග අන්තර්ක්‍රියා කිරීමට අවසර දෙනු ලැබේ:"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; හට ඔබගේ දුරකථනයෙන් මෙම තොරතුරුවලට ප්‍රවේශ වීමට ඉඩ දෙන්න"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"හරස්-උපාංග සේවා"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> ඔබගේ <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> වෙනුවෙන් ඔබගේ උපාංග අතර යෙදුම් ප්‍රවාහ කිරීමට අවසරය ඉල්ලමින් සිටියි"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;මෙයට මයික්‍රෆෝනය, කැමරාව සහ ස්ථාන ප්‍රවේශය සහ &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt; හි අනෙකුත් සංවේදී අවසර ඇතුළත් විය හැකිය.&lt;/p&gt; &lt;p&gt;ඔබට ඔබගේ සැකසීම් තුළ &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt; හිදී ඕනෑම වේලාවක මෙම අවසර වෙනස් කළ හැකිය.&lt;/p&gt;"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"යෙදුම් නිරූපකය"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"වැඩිදුර තොරතුරු බොත්තම"</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"දුරකථනය"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"කෙටිපණිවුඩය"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"සම්බන්‍ධතා"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"දිනදර්ශනය"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"අවට උපාංග"</string>
<string name="permission_storage" msgid="6831099350839392343">"ඡායාරූප සහ මාධ්‍ය"</string>
<string name="permission_notification" msgid="693762568127741203">"දැනුම්දීම්"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"යෙදුම්"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"ඔබේ දුරකථන අංකයට සහ ජාල තොරතුරු වෙත ප්‍රවේශ විය හැක. ඇමතුම් සහ VoIP, හඬ තැපැල්, ඇමතුම් ප්‍රතියෝමුව, සහ ඇමතුම් සටහන් සංස්කරණය සඳහා අවශ්‍යයි."</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"අපගේ සම්බන්‍ධතා ලැයිස්තුව කියවීමට, සෑදීමට, හෝ සංස්කරණ කිරීමට මෙන් ම ඔබේ උපාංගය මත භාවිත කරනු ලබන සියලුම ගිණුම් ලැයිස්තු වෙත ප්‍රවේශ වීමට හැකිය"</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"සම්බන්ධතා, පණිවිඩ සහ ඡායාරූප වැනි තොරතුරු ඇතුළුව සියලු දැනුම්දීම් කියවිය හැකිය"</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"ඔබේ දුරකථනයේ යෙදුම් ප්‍රවාහ කරන්න"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-sk/strings.xml b/packages/CompanionDeviceManager/res/values-sk/strings.xml
index 492a235de004..933c2897fb67 100644
--- a/packages/CompanionDeviceManager/res/values-sk/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-sk/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"Povoľte aplikácii &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; prístup k zariadeniu &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
<string name="profile_name_watch" msgid="576290739483672360">"hodinky"</string>
<string name="chooser_title" msgid="2262294130493605839">"Vyberte profil <xliff:g id="PROFILE_NAME">%1$s</xliff:g>, ktorý bude spravovať aplikácia &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"Aplikácia sa vyžaduje na správu zariadenia <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> bude môcť interagovať s vašimi upozorneniami a získa prístup k povoleniam telefónu, SMS, kontaktov, kalendára, zoznamu hovorov a zariadení v okolí."</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"Aplikácia sa vyžaduje na správu zariadenia <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> bude môcť interagovať s týmito povoleniami:"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"Povoľte aplikácii &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; prístup k týmto informáciám z vášho telefónu"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"Služby pre viacero zariadení"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"Aplikácia <xliff:g id="APP_NAME">%1$s</xliff:g> vyžaduje povolenie na streamovanie aplikácií medzi vašimi zariadeniami v mene tohto zariadenia (<xliff:g id="DEVICE_TYPE">%2$s</xliff:g>)"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Môžu zahŕňať prístup k mikrofónu, kamere a polohe a ďalšie citlivé povolenia v zariadení &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;Tieto povolenia môžete kedykoľvek zmeniť v Nastaveniach v zariadení &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"Ikona aplikácie"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"Tlačidlo Ďalšie informácie"</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"Telefón"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"Kontakty"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"Kalendár"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"Zariadenia v okolí"</string>
<string name="permission_storage" msgid="6831099350839392343">"Fotky a médiá"</string>
<string name="permission_notification" msgid="693762568127741203">"Upozornenia"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"Aplikácie"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"Má prístup k vášmu telefónnemu číslu a informáciám o sieti. Vyžaduje sa na volanie a VoIP, fungovanie hlasovej schránky, presmerovanie hovorov a upravovanie zoznamu hovorov"</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"Môže čítať, vytvárať alebo upravovať náš zoznam kontaktov, ako aj získavať prístup k zoznamu všetkých účtov používaných vo vašom zariadení"</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"Môže čítať všetky upozornenia vrátane informácií, ako sú kontakty, správy a fotky"</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"Streamovať aplikácie telefónu"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-sl/strings.xml b/packages/CompanionDeviceManager/res/values-sl/strings.xml
index 09ebcdfa9111..676da68d3fce 100644
--- a/packages/CompanionDeviceManager/res/values-sl/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-sl/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"Aplikaciji &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; dovolite dostop do naprave &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
<string name="profile_name_watch" msgid="576290739483672360">"ura"</string>
<string name="chooser_title" msgid="2262294130493605839">"Izbira naprave <xliff:g id="PROFILE_NAME">%1$s</xliff:g>, ki jo bo upravljala aplikacija &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"Ta aplikacija je potrebna za upravljanje naprave »<xliff:g id="DEVICE_NAME">%1$s</xliff:g>«. Aplikaciji <xliff:g id="APP_NAME">%2$s</xliff:g> bosta omogočeni interakcija z obvestili in uporaba dovoljenj Telefon, SMS, Stiki, Koledar, Dnevniki klicev in Naprave v bližini."</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"Ta aplikacija je potrebna za upravljanje naprave »<xliff:g id="DEVICE_NAME">%1$s</xliff:g>«. Aplikaciji <xliff:g id="APP_NAME">%2$s</xliff:g> bo omogočena interakcija s temi dovoljenji:"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"Dovolite, da &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; dostopa do teh podatkov v vašem telefonu"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"Storitve za zunanje naprave"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> v imenu naprave »<xliff:g id="DEVICE_TYPE">%2$s</xliff:g>« zahteva dovoljenje za pretočno predvajanje aplikacij v vaših napravah."</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;To lahko vključuje dostop do mikrofona, fotoaparata in lokacije ter druga občutljiva dovoljenja v napravi &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;Ta dovoljenja lahko kadar koli spremenite v nastavitvah v napravi &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"Ikona aplikacije"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"Gumb za več informacij"</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"Telefon"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"Stiki"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"Koledar"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"Naprave v bližini"</string>
<string name="permission_storage" msgid="6831099350839392343">"Fotografije in predstavnost"</string>
<string name="permission_notification" msgid="693762568127741203">"Obvestila"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"Aplikacije"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"Lahko dostopa do telefonske številke in podatkov o omrežju. Obvezno za opravljanje klicev, uporabo storitve VoIP in glasovne pošte, preusmerjanje klicev in urejanje dnevnikov klicev."</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"Lahko bere, ustvarja ali ureja seznam stikov in dostopa do seznama stikov vseh računov, uporabljenih v napravi."</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"Lahko bere vsa obvestila, vključno s podatki, kot so stiki, sporočila in fotografije."</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"Pretočno predvajanje aplikacij telefona"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-sq/strings.xml b/packages/CompanionDeviceManager/res/values-sq/strings.xml
index 3879cb318ef1..7bd86ce7a745 100644
--- a/packages/CompanionDeviceManager/res/values-sq/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-sq/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"Lejo që &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; të ketë qasje te &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
<string name="profile_name_watch" msgid="576290739483672360">"ora inteligjente"</string>
<string name="chooser_title" msgid="2262294130493605839">"Zgjidh një profil <xliff:g id="PROFILE_NAME">%1$s</xliff:g> që do të menaxhohet nga &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"Aplikacioni nevojitet për të menaxhuar profilin tënd të \"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>\". <xliff:g id="APP_NAME">%2$s</xliff:g> do të lejohet të ndërveprojë me njoftimet e tua dhe të ketë qasje te lejet e \"Telefonit\", \"SMS-ve\", \"Kontakteve\", \"Kalendarit\", \"Evidencave të telefonatave\" dhe të \"Pajisjeve në afërsi\"."</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"Aplikacioni nevojitet për të menaxhuar profilin tënd të \"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>\". <xliff:g id="APP_NAME">%2$s</xliff:g> do të lejohet të ndërveprojë me këto leje:"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"Lejo që &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; të ketë qasje në këtë informacion nga telefoni yt"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"Shërbimet mes pajisjeve"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> po kërkon leje në emër të <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> për të transmetuar aplikacione ndërmjet pajisjeve të tua"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Kjo mund të përfshijë qasjen te \"Mikrofoni\", \"Kamera\", \"Vendndodhja\" dhe leje të tjera për informacione delikate në &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&amp;gtTi mund t\'i ndryshosh këto leje në çdo kohë te \"Cilësimet\" në &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"Ikona e aplikacionit"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"Butoni \"Më shumë informacione\""</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"Telefoni"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"Kontaktet"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"Kalendari"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"Pajisjet në afërsi"</string>
<string name="permission_storage" msgid="6831099350839392343">"Fotografitë dhe media"</string>
<string name="permission_notification" msgid="693762568127741203">"Njoftimet"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"Aplikacionet"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"Mund të qaset te informacionet e numrit të telefonit dhe të rrjetit. Kërkohet për të bërë telefonata dhe VoIP, postë zanore, ridrejtim të telefonatës dhe modifikim të evidencave të telefonatave"</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"Mund të lexojë, të krijojë ose të modifikojë listën tënde të kontakteve si dhe të qaset në listën e të gjitha llogarive të përdorura në pajisjen tënde"</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"Mund të lexojë të gjitha njoftimet, duke përfshirë informacione si kontaktet, mesazhet dhe fotografitë"</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"Transmeto aplikacionet e telefonit tënd"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-sr/strings.xml b/packages/CompanionDeviceManager/res/values-sr/strings.xml
index 6ad111f77431..73cf13d6401e 100644
--- a/packages/CompanionDeviceManager/res/values-sr/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-sr/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"Дозволите да &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; приступа уређају &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
<string name="profile_name_watch" msgid="576290739483672360">"сат"</string>
<string name="chooser_title" msgid="2262294130493605839">"Одаберите профил <xliff:g id="PROFILE_NAME">%1$s</xliff:g> којим ће управљати апликација &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"Апликација је потребна за управљање уређајем <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> ће добити дозволу за интеракцију са обавештењима и приступ дозволама за телефон, SMS, контакте, календар, евиденције позива и уређаје у близини."</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"Апликација је потребна за управљање уређајем <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> ће добити дозволу за интеракцију са овим дозволама:"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"Дозволите да &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; приступа овим информацијама са телефона"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"Услуге на више уређаја"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> захтева дозволу у име уређаја <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> за стримовање апликација између уређаја"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;То може да обухвата приступ микрофону, камери и локацији, као и другим осетљивим дозволама на уређају &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;У сваком тренутку можете да промените те дозволе у Подешавањима на уређају &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"Икона апликације"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"Дугме за више информација"</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"Телефон"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"Контакти"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"Календар"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"Уређаји у близини"</string>
<string name="permission_storage" msgid="6831099350839392343">"Слике и медији"</string>
<string name="permission_notification" msgid="693762568127741203">"Обавештења"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"Апликације"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"Може да приступа вашем броју телефона и информацијама о мрежи. Неопходно за упућивање позива и VoIP, говорну пошту, преусмеравање позива и измене евиденције позива"</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"Може да чита, креира или мења листу контаката, као и да приступа листи свих налога који се користе на вашем уређају"</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"Може да чита сва обавештења, укључујући информације попут контаката, порука и слика"</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"Стримујте апликације на телефону"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-sv/strings.xml b/packages/CompanionDeviceManager/res/values-sv/strings.xml
index cd5d8de57c77..ceb7e4021c2f 100644
--- a/packages/CompanionDeviceManager/res/values-sv/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-sv/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"Tillåt att &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; får åtkomst till din &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
<string name="profile_name_watch" msgid="576290739483672360">"klocka"</string>
<string name="chooser_title" msgid="2262294130493605839">"Välj en <xliff:g id="PROFILE_NAME">%1$s</xliff:g> för hantering av &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"Appen behövs för att hantera <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> får tillåtelse att interagera med dina aviseringar och får åtkomst till behörigheterna Telefon, Sms, Kontakter, Kalender, Samtalsloggar och Enheter i närheten."</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"Appen behövs för att hantera <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> får tillåtelse att interagera med följande behörigheter:"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"Ge &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; åtkomstbehörighet till denna information på telefonen"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"Tjänster för flera enheter"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> begär behörighet att låta <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> streama appar mellan enheter"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Det kan gälla behörighet till mikrofon, kamera och plats och åtkomstbehörighet till andra känsliga uppgifter på &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;Du kan när som helst ändra behörigheterna i inställningarna på &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.lt;/p&gt;"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"Appikon"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"Knappen Mer information"</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"Telefon"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"Sms"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"Kontakter"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"Kalender"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"Enheter i närheten"</string>
<string name="permission_storage" msgid="6831099350839392343">"Foton och media"</string>
<string name="permission_notification" msgid="693762568127741203">"Aviseringar"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"Appar"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"Kan få åtkomst till ditt telefonnummer och din nätverksinformation. Krävs för att ringa samtal och VoIP-samtal, röstbrevlådan, omdirigering av samtal och redigering av samtalsloggar"</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"Kan läsa, skapa eller redigera din kontaktlista samt få åtkomst till kontaktlistan för alla konton som används på enheten"</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"Kan läsa alla aviseringar, inklusive information som kontakter, meddelanden och foton"</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"Streama telefonens appar"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-sw/strings.xml b/packages/CompanionDeviceManager/res/values-sw/strings.xml
index a1c354f9f394..856dab1d5244 100644
--- a/packages/CompanionDeviceManager/res/values-sw/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-sw/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"Ruhusu &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ifikie &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; yako"</string>
<string name="profile_name_watch" msgid="576290739483672360">"saa"</string>
<string name="chooser_title" msgid="2262294130493605839">"Chagua <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ili idhibitiwe na &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"Programu hii inahitajika ili udhibiti <xliff:g id="DEVICE_NAME">%1$s</xliff:g> yako. <xliff:g id="APP_NAME">%2$s</xliff:g> itaruhusiwa kufikia arifa zako na kufikia ruhusa zako za Simu, SMS, Anwani, Kalenda, Rekodi za nambari za simu na Vifaa vilivyo karibu."</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"Programu hii inahitajika ili udhibiti <xliff:g id="DEVICE_NAME">%1$s</xliff:g> yako. <xliff:g id="APP_NAME">%2$s</xliff:g> itaruhusiwa kufikia ruhusa hizi:"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"Ruhusu &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ifikie maelezo haya kutoka kwenye simu yako"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"Huduma za kifaa kilichounganishwa kwingine"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"Programu ya <xliff:g id="APP_NAME">%1$s</xliff:g> inaomba ruhusa kwa niaba ya <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> yako ili itiririshe programu kati ya vifaa vyako"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Hii huenda ikajumuisha ufikiaji wa Maikrofoni, Kamera na Mahali, pamoja na ruhusa nyingine nyeti kwenye &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;Unaweza kubadilisha ruhusa hizi muda wowote katika Mipangilio yako kwenye &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"Aikoni ya Programu"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"Kitufe cha Maelezo Zaidi"</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"Simu"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"Anwani"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"Kalenda"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"Vifaa vilivyo karibu"</string>
<string name="permission_storage" msgid="6831099350839392343">"Picha na maudhui"</string>
<string name="permission_notification" msgid="693762568127741203">"Arifa"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"Programu"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"Inaweza kufikia nambari yako ya simu na maelezo ya mtandao. Inahitajika kwa ajili ya kupiga simu na VoIP, ujumbe wa sauti, uelekezaji wa simu kwingine na kubadilisha rekodi za nambari za simu"</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"Inaweza kusoma, kuunda au kubadilisha orodha yetu ya anwani na pia kufikia orodha ya akaunti zote zinazotumiwa kwenye kifaa chako"</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"Inaweza kusoma arifa zote, ikiwa ni pamoja na maelezo kama vile anwani, ujumbe na picha"</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"Tiririsha programu za simu yako"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-te/strings.xml b/packages/CompanionDeviceManager/res/values-te/strings.xml
index b44b59c1e505..3a09d68c7e09 100644
--- a/packages/CompanionDeviceManager/res/values-te/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-te/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"మీ &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;ను యాక్సెస్ చేయడానికి &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;ను అనుమతించండి"</string>
<string name="profile_name_watch" msgid="576290739483672360">"వాచ్"</string>
<string name="chooser_title" msgid="2262294130493605839">"&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; ద్వారా మేనేజ్ చేయబడటానికి ఒక <xliff:g id="PROFILE_NAME">%1$s</xliff:g>ను ఎంచుకోండి"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"మీ <xliff:g id="DEVICE_NAME">%1$s</xliff:g>‌ను మేనేజ్ చేయడానికి ఈ యాప్ అవసరం. మీ నోటిఫికేషన్‌లతో ఇంటరాక్ట్ అవ్వడానికి, అలాగే మీ ఫోన్, SMS, కాంటాక్ట్‌లు, క్యాలెండర్, కాల్ లాగ్‌లు, సమీపంలోని పరికరాల అనుమతులను యాక్సెస్ చేయడానికి <xliff:g id="APP_NAME">%2$s</xliff:g> అనుమతించబడుతుంది."</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"మీ <xliff:g id="DEVICE_NAME">%1$s</xliff:g>‌ను మేనేజ్ చేయడానికి ఈ యాప్ అవసరం. ఈ అనుమతులతో ఇంటరాక్ట్ అవ్వడానికి <xliff:g id="APP_NAME">%2$s</xliff:g> అనుమతించబడుతుంది:"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"మీ ఫోన్ నుండి ఈ సమాచారాన్ని యాక్సెస్ చేయడానికి &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; యాప్‌ను అనుమతించండి"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"Cross-device services"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"మీ పరికరాల మధ్య యాప్‌లను స్ట్రీమ్ చేయడానికి <xliff:g id="APP_NAME">%1$s</xliff:g> మీ <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> తరఫున అనుమతిని రిక్వెస్ట్ చేస్తోంది"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;&lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;లో మైక్రోఫోన్, కెమెరా, లొకేషన్ యాక్సెస్, ఇంకా ఇతర గోప్యమైన సమాచార యాక్సెస్ అనుమతులు ఇందులో ఉండవచ్చు.&lt;/p&gt; &lt;p&gt;మీరు &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;లో మీ సెట్టింగ్‌లలో ఎప్పుడైనా ఈ అనుమతులను మార్చవచ్చు.&lt;/p&gt;"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"యాప్ చిహ్నం"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"మరింత సమాచారం బటన్"</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"ఫోన్"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"కాంటాక్ట్‌లు"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"క్యాలెండర్"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"సమీపంలోని పరికరాలు"</string>
<string name="permission_storage" msgid="6831099350839392343">"ఫోటోలు, మీడియా"</string>
<string name="permission_notification" msgid="693762568127741203">"నోటిఫికేషన్‌లు"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"యాప్‌లు"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"మీ ఫోన్ నంబర్, ఇంకా నెట్‌వర్క్ సమాచారాన్ని యాక్సెస్ చేయగలదు. కాల్స్ చేయడానికి, VoIP కాల్స్ చేయడానికి, వాయిస్ మెయిల్‌కు, కాల్ మళ్లింపునకు, ఇంకా కాల్ లాగ్‌లను ఎడిట్ చేయడానికి ఇది అవసరం"</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"మీ కాంటాక్ట్ లిస్ట్‌ను చదవడం, క్రియేట్ చేయడం, లేదా ఎడిట్ చేయడంతో పాటు మీ పరికరంలో ఉపయోగించబడే ఖాతాలన్నింటి లిస్ట్‌ను యాక్సెస్ కూడా చేయగలదు"</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"కాంటాక్ట్‌లు, మెసేజ్‌లు, ఫోటోల వంటి సమాచారంతో సహా అన్ని నోటిఫికేషన్‌లను చదవగలదు"</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"మీ ఫోన్‌లోని యాప్‌లను స్ట్రీమ్ చేయండి"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-th/strings.xml b/packages/CompanionDeviceManager/res/values-th/strings.xml
index 237d129496bc..522c29f3323e 100644
--- a/packages/CompanionDeviceManager/res/values-th/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-th/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"อนุญาตให้ &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; เข้าถึง &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; ของคุณ"</string>
<string name="profile_name_watch" msgid="576290739483672360">"นาฬิกา"</string>
<string name="chooser_title" msgid="2262294130493605839">"เลือก<xliff:g id="PROFILE_NAME">%1$s</xliff:g>ที่จะให้มีการจัดการโดย &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"ต้องใช้แอปนี้ในการจัดการ<xliff:g id="DEVICE_NAME">%1$s</xliff:g> <xliff:g id="APP_NAME">%2$s</xliff:g> จะได้รับอนุญาตให้โต้ตอบกับการแจ้งเตือนและได้รับสิทธิ์เข้าถึงโทรศัพท์, SMS, รายชื่อติดต่อ, ปฏิทิน, บันทึกการโทร และอุปกรณ์ที่อยู่ใกล้เคียง"</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"ต้องใช้แอปนี้ในการจัดการ<xliff:g id="DEVICE_NAME">%1$s</xliff:g> <xliff:g id="APP_NAME">%2$s</xliff:g> จะได้รับอนุญาตให้โต้ตอบกับสิทธิ์เหล่านี้"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"อนุญาตให้ &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; เข้าถึงข้อมูลนี้จากโทรศัพท์ของคุณ"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"บริการหลายอุปกรณ์"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> กำลังขอสิทธิ์ในนามของ <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> เพื่อสตรีมแอประหว่างอุปกรณ์ต่างๆ ของคุณ"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;โดยอาจรวมถึงสิทธิ์เข้าถึงไมโครโฟน กล้อง และตำแหน่ง ตลอดจนสิทธิ์ที่มีความละเอียดอ่อนอื่นๆ ใน &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;คุณเปลี่ยนแปลงสิทธิ์เหล่านี้ได้ทุกเมื่อในการตั้งค่าใน &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"ไอคอนแอป"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"ปุ่มข้อมูลเพิ่มเติม"</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"โทรศัพท์"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"รายชื่อติดต่อ"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"ปฏิทิน"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"อุปกรณ์ที่อยู่ใกล้เคียง"</string>
<string name="permission_storage" msgid="6831099350839392343">"รูปภาพและสื่อ"</string>
<string name="permission_notification" msgid="693762568127741203">"การแจ้งเตือน"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"แอป"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"สามารถเข้าถึงหมายเลขโทรศัพท์และข้อมูลเครือข่ายของคุณ จำเป็นสำหรับการโทรและ VoIP, ข้อความเสียง, การเปลี่ยนเส้นทางการโทร และการแก้ไขบันทึกการโทร"</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"สามารถอ่าน สร้าง หรือแก้ไขข้อมูลรายชื่อติดต่อของเรา รวมทั้งเข้าถึงข้อมูลรายชื่อติดต่อของทุกบัญชีที่ใช้ในอุปกรณ์ของคุณ"</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"สามารถอ่านการแจ้งเตือนทั้งหมด รวมถึงข้อมูลอย่างรายชื่อติดต่อ ข้อความ และรูปภาพ"</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"สตรีมแอปของโทรศัพท์คุณ"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-tl/strings.xml b/packages/CompanionDeviceManager/res/values-tl/strings.xml
index 86e6898012bd..79c23aa67d46 100644
--- a/packages/CompanionDeviceManager/res/values-tl/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-tl/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"Payagan ang &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; na i-access ang iyong &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
<string name="profile_name_watch" msgid="576290739483672360">"relo"</string>
<string name="chooser_title" msgid="2262294130493605839">"Pumili ng <xliff:g id="PROFILE_NAME">%1$s</xliff:g> para pamahalaan ng &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"Kailangan ang app para mapamahalaan ang iyong <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. Papayagan ang <xliff:g id="APP_NAME">%2$s</xliff:g> na makipag-ugnayan sa mga notification mo at i-access ang iyong pahintulot sa Telepono, SMS, Mga Contact, Kalendaryo, Log ng mga tawag, at Mga kalapit na device."</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"Kailangan ang app para mapamahalaan ang iyong <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. Papayagan ang <xliff:g id="APP_NAME">%2$s</xliff:g> na makipag-ugnayan sa mga pahintulot na ito:"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"Payagan ang &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; na i-access ang impormasyong ito sa iyong telepono"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"Mga cross-device na serbisyo"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"Ang <xliff:g id="APP_NAME">%1$s</xliff:g> ay humihiling ng pahintulot sa ngalan ng iyong <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> para mag-stream ng mga app sa pagitan ng mga device mo"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Posibleng kabilang dito ang access sa Mikropono, Camera, at Lokasyon, at iba pang pahintulot sa sensitibong impormasyon sa &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;Puwede mong baguhin ang mga pahintulot na ito anumang oras sa iyong Mga Setting sa &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"Icon ng App"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"Button ng Dagdag Impormasyon"</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"Telepono"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"Mga Contact"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"Calendar"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"Mga kalapit na device"</string>
<string name="permission_storage" msgid="6831099350839392343">"Mga larawan at media"</string>
<string name="permission_notification" msgid="693762568127741203">"Mga Notification"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"Mga App"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"Naa-access ang iyong numero ng telepono at impormasyon ng network. Kinakailangan para sa mga pagtawag at VoIP, voicemail, pag-redirect ng tawag, at pag-edit ng mga log ng tawag"</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"Nakaka-read, nakakagawa, o nakakapag-edit ng aming listahan ng contact, pati na rin nakaka-access ng listahan ng lahat ng account na ginamit sa iyong device"</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"Magbasa ng lahat ng notification, kabilang ang impormasyon gaya ng mga contact, mensahe, at larawan"</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"I-stream ang mga app ng iyong telepono"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-tr/strings.xml b/packages/CompanionDeviceManager/res/values-tr/strings.xml
index 2cb03f765e6a..ea4e20ac1f26 100644
--- a/packages/CompanionDeviceManager/res/values-tr/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-tr/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"&lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; cihazınıza erişmesi için &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; uygulamasına izin verin"</string>
<string name="profile_name_watch" msgid="576290739483672360">"saat"</string>
<string name="chooser_title" msgid="2262294130493605839">"&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; tarafından yönetilecek bir <xliff:g id="PROFILE_NAME">%1$s</xliff:g> seçin"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"Bu uygulama, <xliff:g id="DEVICE_NAME">%1$s</xliff:g> cihazınızın yönetilmesi için gereklidir. <xliff:g id="APP_NAME">%2$s</xliff:g> adlı uygulamanın bildirimlerinizle etkileşimde bulunup Telefon, SMS, Kişiler, Takvim, Arama kayıtları ve Yakındaki cihazlar izinlerinize erişmesine izin verilir."</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"Bu uygulama, <xliff:g id="DEVICE_NAME">%1$s</xliff:g> cihazınızın yönetilmesi için gereklidir. <xliff:g id="APP_NAME">%2$s</xliff:g> uygulamasının şu izinlerle etkileşime girmesine izin verilir:"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; uygulamasının, telefonunuzdaki bu bilgilere erişmesine izin verin"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"Cihazlar arası hizmetler"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g>, cihazlarınız arasında uygulama akışı gerçekleştirmek için <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> cihazınız adına izin istiyor"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Mikrofon, Kamera ve Konum erişiminin yanı sıra &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt; cihazındaki diğer hassas bilgilere erişim izinleri de bu kapsamda olabilir.&lt;/p&gt; &lt;p&gt;Bu izinleri istediğiniz zaman &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt; cihazındaki Ayarlar bölümünden değiştirebilirsiniz.&lt;/p&gt;"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"Uygulama Simgesi"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"Daha Fazla Bilgi Düğmesi"</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"Telefon"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"Kişiler"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"Takvim"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"Yakındaki cihazlar"</string>
<string name="permission_storage" msgid="6831099350839392343">"Fotoğraflar ve medya"</string>
<string name="permission_notification" msgid="693762568127741203">"Bildirimler"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"Uygulamalar"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"Telefon numaranıza ve ağ bilgilerinize erişebilir. Arama, VoIP, sesli mesaj, arama yönlendirme gibi işlemleri gerçekleştirmek ve arama kayıtlarını düzenlemek için gereklidir"</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"Kişi listesini okuyabilir, oluşturabilir veya düzenleyebilir, ayrıca cihazınızda kullanılan tüm hesapların listesine erişebilir"</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"Kişiler, mesajlar ve fotoğraflar da dahil olmak üzere tüm bildirimleri okuyabilir"</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"Telefonunuzun uygulamalarını yayınlama"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-uk/strings.xml b/packages/CompanionDeviceManager/res/values-uk/strings.xml
index 905c62a7fb38..79b03ea2dadc 100644
--- a/packages/CompanionDeviceManager/res/values-uk/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-uk/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"Надати додатку &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; доступ до пристрою &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
<string name="profile_name_watch" msgid="576290739483672360">"годинник"</string>
<string name="chooser_title" msgid="2262294130493605839">"Виберіть <xliff:g id="PROFILE_NAME">%1$s</xliff:g>, яким керуватиме додаток &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"Цей додаток потрібен, щоб керувати пристроєм \"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>\". Додаток <xliff:g id="APP_NAME">%2$s</xliff:g> зможе взаємодіяти з вашими сповіщеннями й отримає дозволи \"Телефон\", \"SMS\", \"Контакти\", \"Календар\", \"Журнали викликів\" і \"Пристрої поблизу\"."</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"Цей додаток потрібен, щоб керувати пристроєм \"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>\". Додаток <xliff:g id="APP_NAME">%2$s</xliff:g> зможе взаємодіяти з такими дозволами:"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"Надайте додатку &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; доступ до цієї інформації з телефона"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"Сервіси для кількох пристроїв"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"Додаток <xliff:g id="APP_NAME">%1$s</xliff:g> від імені вашого пристрою <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> запитує дозвіл на трансляцію додатків між вашими пристроями"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Це може бути доступ до мікрофона, камери та геоданих, а також до іншої конфіденційної інформації на пристрої &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;Ви можете будь-коли змінити ці дозволи в налаштуваннях на пристрої &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"Значок додатка"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"Кнопка \"Докладніше\""</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"Телефон"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"Контакти"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"Календар"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"Пристрої поблизу"</string>
<string name="permission_storage" msgid="6831099350839392343">"Фотографії та медіафайли"</string>
<string name="permission_notification" msgid="693762568127741203">"Сповіщення"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"Додатки"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"Може переглядати ваш номер телефону й інформацію про мережу. Потрібно для здійснення викликів і зв’язку через VoIP, голосової пошти, переадресації викликів і редагування журналів викликів"</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"Може читати, створювати або редагувати список контактів, а також переглядати список усіх облікових записів, які використовуються на вашому пристрої"</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"Може читати всі сповіщення, зокрема таку інформацію, як контакти, повідомлення та фотографії"</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"Транслювати додатки телефона"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-ur/strings.xml b/packages/CompanionDeviceManager/res/values-ur/strings.xml
index ed1453ccc4d1..71473f701229 100644
--- a/packages/CompanionDeviceManager/res/values-ur/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ur/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"‏‎&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;‎ کو اپنے ‎&lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;‎ تک رسائی کی اجازت دیں"</string>
<string name="profile_name_watch" msgid="576290739483672360">"دیکھیں"</string>
<string name="chooser_title" msgid="2262294130493605839">"‏&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; کے ذریعے نظم کئے جانے کیلئے <xliff:g id="PROFILE_NAME">%1$s</xliff:g> کو منتخب کریں"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"‏آپ کے <xliff:g id="DEVICE_NAME">%1$s</xliff:g> کا نظم کرنے کے لئے ایپ کی ضرورت ہے۔ <xliff:g id="APP_NAME">%2$s</xliff:g> کو آپ کی اطلاعات کے ساتھ تعامل کرنے اور آپ کے فون، SMS، رابطوں، کیلنڈر، کال لاگز اور قریبی آلات کی اجازتوں تک رسائی کی اجازت ہوگی۔"</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"آپ کے <xliff:g id="DEVICE_NAME">%1$s</xliff:g> کا نظم کرنے کے لئے ایپ کی ضرورت ہے۔ <xliff:g id="APP_NAME">%2$s</xliff:g> کو ان اجازتوں کے ساتھ تعامل کرنے کی اجازت ہوگی:"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"‏اپنے فون سے ان معلومات تک رسائی حاصل کرنے کی &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; کو اجازت دیں"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"کراس ڈیوائس سروسز"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> ایپ آپ کے <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> کی جانب سے آپ کے آلات کے درمیان ایپس کی سلسلہ بندی کرنے کی اجازت کی درخواست کر رہی ہے"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"‏&lt;p&gt;اس میں مائیکروفون، کیمرا اور مقام تک رسائی اور ;‎&lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&amp;gt پر دیگر حساس اجازتیں شامل ہو سکتی ہیں۔&lt;/p&gt; &lt;p&gt;آپ ‎&lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>;&lt;/strong&amp;gt پر کسی بھی وقت اپنی ترتیبات میں ان اجازتوں کو تبدیل کر سکتے ہیں۔&lt;/p&gt;"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"ایپ کا آئیکن"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"مزید معلومات کا بٹن"</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"فون"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"رابطے"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"کیلنڈر"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"قریبی آلات"</string>
<string name="permission_storage" msgid="6831099350839392343">"تصاویر اور میڈیا"</string>
<string name="permission_notification" msgid="693762568127741203">"اطلاعات"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"ایپس"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"‏آپ کے فون نمبر اور نیٹ ورک کی معلومات تک رسائی حاصل کر سکتی ہے۔ کالز کرنے اور VoIP، صوتی میل، کال ری ڈائریکٹ، اور کال لاگز میں ترمیم کرنے کے لیے درکار ہے"</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"ہماری رابطوں کی فہرست پڑھ سکتی ہے، اسے تخلیق سکتی ہے یا اس میں ترمیم کر سکتی ہے، نیز آپ کے آلے پر استعمال ہونے والے تمام اکاؤنٹس کی فہرست تک رسائی حاصل کر سکتی ہے"</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"رابطوں، پیغامات اور تصاویر جیسی معلومات سمیت تمام اطلاعات پڑھ سکتے ہیں"</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"اپنے فون کی ایپس کی سلسلہ بندی کریں"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-uz/strings.xml b/packages/CompanionDeviceManager/res/values-uz/strings.xml
index bc945095d70a..721a338a124c 100644
--- a/packages/CompanionDeviceManager/res/values-uz/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-uz/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"&lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; qurilmasiga kirish uchun &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ilovasiga ruxsat bering"</string>
<string name="profile_name_watch" msgid="576290739483672360">"soat"</string>
<string name="chooser_title" msgid="2262294130493605839">"&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; boshqaradigan <xliff:g id="PROFILE_NAME">%1$s</xliff:g> qurilmasini tanlang"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"Ilova <xliff:g id="DEVICE_NAME">%1$s</xliff:g> profilini boshqarish uchun kerak. <xliff:g id="APP_NAME">%2$s</xliff:g> ilovasiga bildirishnomalar bilan ishlash va telefon, SMS, kontaktlar, taqvim, chaqiruvlar jurnali va yaqin-atrofdagi qurilmalarga kirishga ruxsat beriladi."</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"Ilova <xliff:g id="DEVICE_NAME">%1$s</xliff:g> profilini boshqarish uchun kerak. <xliff:g id="APP_NAME">%2$s</xliff:g> ilovasiga quyidagi ruxsatlar bilan ishlashga ruxsat beriladi:"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ilovasiga telefondagi ushbu maʼlumot uchun ruxsat bering"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"Qurilmalararo xizmatlar"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"Qurilamalararo ilovalar strimingi uchun <xliff:g id="APP_NAME">%1$s</xliff:g> ilovasi <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> nomidan ruxsat soʻramoqda"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Bunga &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt; qurilmasidagi Mikrofon, Kamera, Joylashuv kabi muhim ruxsatlar kirishi mumkin.&lt;/p&gt; &lt;p&gt;Bu ruxsatlarni istalgan vaqt &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt; Sozlamalari orqali oʻzgartirish mumkin.&lt;/p&gt;"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"Ilova belgisi"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"Batafsil axborot tugmasi"</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"Telefon"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"Kontaktlar"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"Taqvim"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"Atrofdagi qurilmalar"</string>
<string name="permission_storage" msgid="6831099350839392343">"Suratlar va media"</string>
<string name="permission_notification" msgid="693762568127741203">"Bildirishnomalar"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"Ilovalar"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"Telefon raqamingiz va tarmoq maʼlumotlariga kira oladi. Telefon qilish va VoIP, ovozli xabar, chaqiruvlarni yoʻnaltirish va chaqiruvlar jurnallarini tahrirlash uchun talab qilinadi"</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"Kontaktlar roʻyxatini oʻqishi, yaratishi yoki tahrirlashi, shuningdek, qurilmangizda foydalaniladigan barcha hisoblar roʻyxatiga kirishi mumkin"</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"Barcha bildirishnomalarni, jumladan, kontaktlar, xabarlar va suratlarni oʻqishi mumkin"</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"Telefondagi ilovalarni translatsiya qilish"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-vi/strings.xml b/packages/CompanionDeviceManager/res/values-vi/strings.xml
index c5dd928fa84c..cb9e558f71ca 100644
--- a/packages/CompanionDeviceManager/res/values-vi/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-vi/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"Cho phép &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; truy cập &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; của bạn"</string>
<string name="profile_name_watch" msgid="576290739483672360">"đồng hồ"</string>
<string name="chooser_title" msgid="2262294130493605839">"Chọn một <xliff:g id="PROFILE_NAME">%1$s</xliff:g> sẽ do &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; quản lý"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"Cần có ứng dụng này để quản lý <xliff:g id="DEVICE_NAME">%1$s</xliff:g> của bạn. <xliff:g id="APP_NAME">%2$s</xliff:g> sẽ được phép tương tác với thông báo và truy cập vào Điện thoại, SMS, Danh bạ, Lịch, Nhật ký cuộc gọi và Thiết bị ở gần."</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"Cần có ứng dụng này để quản lý <xliff:g id="DEVICE_NAME">%1$s</xliff:g> của bạn. <xliff:g id="APP_NAME">%2$s</xliff:g> được quyền tương tác với những chức năng sau:"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"Cho phép &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; truy cập vào thông tin này trên điện thoại của bạn"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"Dịch vụ trên nhiều thiết bị"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> đang yêu cầu quyền thay cho <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> để truyền trực tuyến ứng dụng giữa các thiết bị của bạn"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Những quyền này có thể bao gồm quyền truy cập vào micrô, máy ảnh và thông tin vị trí, cũng như các quyền truy cập thông tin nhạy cảm khác trên &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;Bạn có thể thay đổi những quyền này bất cứ lúc nào trong phần Cài đặt trên &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"Biểu tượng ứng dụng"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"Nút thông tin khác"</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"Điện thoại"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"Tin nhắn SMS"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"Danh bạ"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"Lịch"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"Thiết bị ở gần"</string>
<string name="permission_storage" msgid="6831099350839392343">"Ảnh và nội dung nghe nhìn"</string>
<string name="permission_notification" msgid="693762568127741203">"Thông báo"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"Ứng dụng"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"Có thể truy cập vào thông tin mạng và số điện thoại của bạn. Điện thoại cần được cấp quyền này để gọi điện và gọi bằng dịch vụ VoIP, soạn thư thoại, chuyển hướng cuộc gọi, đồng thời chỉnh sửa nhật ký cuộc gọi"</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"Có thể tạo, đọc, chỉnh sửa, đồng thời truy cập danh bạ của mọi tài khoản được sử dụng trên thiết bị"</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"Có thể đọc tất cả các thông báo, kể cả những thông tin như danh bạ, tin nhắn và ảnh"</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"Truyền các ứng dụng trên điện thoại của bạn"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-zh-rCN/strings.xml b/packages/CompanionDeviceManager/res/values-zh-rCN/strings.xml
index b08e8f4717ef..dad470930fd7 100644
--- a/packages/CompanionDeviceManager/res/values-zh-rCN/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-zh-rCN/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"允许&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;访问您的&lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
<string name="profile_name_watch" msgid="576290739483672360">"手表"</string>
<string name="chooser_title" msgid="2262294130493605839">"选择要由&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;管理的<xliff:g id="PROFILE_NAME">%1$s</xliff:g>"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"需要使用此应用,才能管理您的<xliff:g id="DEVICE_NAME">%1$s</xliff:g>。<xliff:g id="APP_NAME">%2$s</xliff:g>将能与通知交互,并可获得电话、短信、通讯录、日历、通话记录和附近设备的访问权限。"</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"需要使用此应用,才能管理您的<xliff:g id="DEVICE_NAME">%1$s</xliff:g>。<xliff:g id="APP_NAME">%2$s</xliff:g>可与以下权限交互:"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"允许“<xliff:g id="APP_NAME">%1$s</xliff:g>”&lt;strong&gt;&lt;/strong&gt;访问您手机中的这项信息"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"跨设备服务"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"“<xliff:g id="APP_NAME">%1$s</xliff:g>”正代表您的<xliff:g id="DEVICE_TYPE">%2$s</xliff:g>请求在您的设备之间流式传输应用内容"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;这可能包括&lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;的麦克风、摄像头和位置信息访问权限,以及其他敏感权限。&lt;/p&gt; &lt;p&gt;您可以随时在&lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;的“设置”中更改这些权限。&lt;/p&gt;"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"应用图标"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"更多信息按钮"</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"手机"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"短信"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"通讯录"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"日历"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"附近的设备"</string>
<string name="permission_storage" msgid="6831099350839392343">"照片和媒体内容"</string>
<string name="permission_notification" msgid="693762568127741203">"通知"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"应用"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"可以访问您的电话号码和网络信息。具备此权限才能实现电话拨打以及 VoIP、语音信箱、电话转接和通话记录编辑功能"</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"可以读取、创建或修改您的联系人列表,以及访问您设备上使用的所有帐号的联系人列表"</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"可以读取所有通知,包括合同、消息和照片等信息"</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"流式传输手机上的应用"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-zh-rHK/strings.xml b/packages/CompanionDeviceManager/res/values-zh-rHK/strings.xml
index 94ebb3d65c19..50c4214a8889 100644
--- a/packages/CompanionDeviceManager/res/values-zh-rHK/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-zh-rHK/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"允許&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; 存取您的 &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
<string name="profile_name_watch" msgid="576290739483672360">"手錶"</string>
<string name="chooser_title" msgid="2262294130493605839">"選擇由 &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; 管理的<xliff:g id="PROFILE_NAME">%1$s</xliff:g>"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"必須使用此應用程式,才能管理「<xliff:g id="DEVICE_NAME">%1$s</xliff:g>」。「<xliff:g id="APP_NAME">%2$s</xliff:g>」將可存取通知、電話、短訊、通訊錄和日曆、通話記錄和附近的裝置權限。"</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"必須使用此應用程式,才能管理「<xliff:g id="DEVICE_NAME">%1$s</xliff:g>」。「<xliff:g id="APP_NAME">%2$s</xliff:g>」將可存取以下權限:"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"允許「<xliff:g id="APP_NAME">%1$s</xliff:g>」&lt;strong&gt;&lt;/strong&gt;存取您手機中的這項資料"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"跨裝置服務"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」正在為 <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> 要求權限,以在裝置之間串流應用程式內容"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;這可能包括 &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt; 的麥克風、相機和位置存取權和其他敏感資料權限。您隨時可透過 &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; 變更這些權限。"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"應用程式圖示"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"「更多資料」按鈕"</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"手機"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"短訊"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"通訊錄"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"日曆"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"附近的裝置"</string>
<string name="permission_storage" msgid="6831099350839392343">"相片和媒體"</string>
<string name="permission_notification" msgid="693762568127741203">"通知"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"應用程式"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"可存取您的電話號碼及網絡資訊。必須授予此權限才可撥打電話和 VoIP 通話、留言、轉駁來電及編輯通話記錄"</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"可讀取、建立或編輯通訊錄,以及存取您裝置上所有用過的帳戶清單"</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"可以讀取所有通知,包括聯絡人、訊息和電話等資訊"</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"串流播放手機應用程式內容"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-zh-rTW/strings.xml b/packages/CompanionDeviceManager/res/values-zh-rTW/strings.xml
index adf8708f53f0..7cbd9a775a65 100644
--- a/packages/CompanionDeviceManager/res/values-zh-rTW/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-zh-rTW/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"允許「<xliff:g id="APP_NAME">%1$s</xliff:g>」&lt;strong&gt;&lt;/strong&gt;存取「<xliff:g id="DEVICE_NAME">%2$s</xliff:g>」&lt;strong&gt;&lt;/strong&gt;"</string>
<string name="profile_name_watch" msgid="576290739483672360">"手錶"</string>
<string name="chooser_title" msgid="2262294130493605839">"選擇要讓「<xliff:g id="APP_NAME">%2$s</xliff:g>」&lt;strong&gt;&lt;/strong&gt;管理的<xliff:g id="PROFILE_NAME">%1$s</xliff:g>"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"你必須使用這個應用程式,才能管理「<xliff:g id="DEVICE_NAME">%1$s</xliff:g>」。「<xliff:g id="APP_NAME">%2$s</xliff:g>」將可存取通知、電話、簡訊、聯絡人和日曆、通話記錄和鄰近裝置的權限。"</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"你必須使用這個應用程式,才能管理「<xliff:g id="DEVICE_NAME">%1$s</xliff:g>」。「<xliff:g id="APP_NAME">%2$s</xliff:g>」將可與下列權限互動:"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"允許「<xliff:g id="APP_NAME">%1$s</xliff:g>」&lt;strong&gt;&lt;/strong&gt;存取手機中的這項資訊"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"跨裝置服務"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」正在代表你的「<xliff:g id="DEVICE_TYPE">%2$s</xliff:g>」要求必要權限,以便在裝置之間串流傳輸應用程式內容"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;這可能包括「<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>」&lt;strong&gt;&lt;/strong&gt;.&lt;/p&gt;的麥克風、相機和位置資訊存取權和其他機密權限。&lt;/p&gt; &lt;p&gt;你隨時可透過「<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>」&lt;strong&gt;&lt;/strong&gt;的設定變更這些權限。&lt;/p&gt;"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"應用程式圖示"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"更多資訊按鈕"</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"電話"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"簡訊"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"聯絡人"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"日曆"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"鄰近裝置"</string>
<string name="permission_storage" msgid="6831099350839392343">"相片和媒體"</string>
<string name="permission_notification" msgid="693762568127741203">"通知"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"應用程式"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"可存取你的電話號碼和網路資訊。你必須授予這項權限,才能撥打電話和進行 VoIP 通話、聽取語音留言、轉接來電,以及編輯通話記錄"</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"可讀取、建立或編輯你的聯絡人清單,還能存取裝置上所有帳戶的聯絡人清單"</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"可讀取所有通知,包括聯絡人、訊息和電話等資訊"</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"串流傳輸手機應用程式內容"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-zu/strings.xml b/packages/CompanionDeviceManager/res/values-zu/strings.xml
index c4e634c5ad0c..231f71c7da89 100644
--- a/packages/CompanionDeviceManager/res/values-zu/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-zu/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"Vumela i-&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ukuthi ifinyelele i-&lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; yakho"</string>
<string name="profile_name_watch" msgid="576290739483672360">"buka"</string>
<string name="chooser_title" msgid="2262294130493605839">"Khetha i-<xliff:g id="PROFILE_NAME">%1$s</xliff:g> ezophathwa yi-&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"I-app iyadingeka ukuphatha i-<xliff:g id="DEVICE_NAME">%1$s</xliff:g> yakho. I-<xliff:g id="APP_NAME">%2$s</xliff:g> izovunyelwa ukuthi ihlanganyele nezaziso zakho futhi ifinyelele Ifoni yakho, i-SMS, Oxhumana nabo, Ikhalenda, Amarekhodi wamakholi Nezimvume zamadivayisi aseduze."</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"I-app iyadingeka ukuphatha i-<xliff:g id="DEVICE_NAME">%1$s</xliff:g> yakho. <xliff:g id="APP_NAME">%2$s</xliff:g> uzovunyelwa ukusebenzisana nalezi zimvume:"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"Vumela i-&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ifinyelele lolu lwazi kusukela efonini yakho"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"Amasevisi amadivayisi amaningi"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"I-<xliff:g id="APP_NAME">%1$s</xliff:g> icela imvume esikhundleni se-<xliff:g id="DEVICE_TYPE">%2$s</xliff:g> yakho ukuze isakaze-bukhoma ama-app phakathi kwamadivayisi akho"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Lokhu kungase kuhlanganisa Imakrofoni, Ikhamera, kanye Nokufinyelela kwendawo, kanye nezinye izimvume ezibucayi &lt;strong&gt;ku-<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;Ungashintsha lezi zimvume nganoma yisiphi isikhathi Kumasethingi akho &lt;strong&gt;ku-<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"Isithonjana Se-app"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"Inkinobho Yolwazi Olwengeziwe"</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"Ifoni"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"I-SMS"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"Oxhumana nabo"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"Ikhalenda"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"Amadivayisi aseduze"</string>
<string name="permission_storage" msgid="6831099350839392343">"Izithombe nemidiya"</string>
<string name="permission_notification" msgid="693762568127741203">"Izaziso"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"Ama-app"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"Ingakwazi ukufinyelela inombolo yakho yefoni kanye nolwazi lwenethiwekhi. Iyadingeka ekwenzeni amakholi ne-VoIP, ivoyisimeyili, ukuqondisa kabusha ikholi, nokuhlela amarekhodi amakholi"</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"Angafunda, asungule, noma ahlele uhlu lwethu loxhumana nabo, futhi afinyelele uhlu lwawo wonke ama-akhawunti asetshenziswa kudivayisi yakho"</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"Ingafunda zonke izaziso, okubandakanya ulwazi olufana noxhumana nabo, imilayezo, nezithombe"</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"Sakaza ama-app wefoni yakho"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CredentialManager/res/drawable/ic_passkeys_onboarding.png b/packages/CredentialManager/res/drawable/ic_passkeys_onboarding.png
new file mode 100644
index 000000000000..388d09837252
--- /dev/null
+++ b/packages/CredentialManager/res/drawable/ic_passkeys_onboarding.png
Binary files differ
diff --git a/packages/CredentialManager/res/drawable/ic_passkeys_onboarding_device.xml b/packages/CredentialManager/res/drawable/ic_passkeys_onboarding_device.xml
new file mode 100644
index 000000000000..9e4f42440628
--- /dev/null
+++ b/packages/CredentialManager/res/drawable/ic_passkeys_onboarding_device.xml
@@ -0,0 +1,32 @@
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<vector
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ tools:ignore="VectorPath"
+ android:name="vector"
+ android:width="21dp"
+ android:height="17dp"
+ android:viewportWidth="21"
+ android:viewportHeight="17">
+ <path
+ android:name="path"
+ android:pathData="M 4 2.941 L 20 2.941 L 20 0.941 L 4 0.941 C 2.9 0.941 2 1.841 2 2.941 L 2 13.941 L 0 13.941 L 0 16.941 L 11 16.941 L 11 13.941 L 4 13.941 L 4 2.941 Z M 20 4.941 L 14 4.941 C 13.45 4.941 13 5.391 13 5.941 L 13 15.941 C 13 16.491 13.45 16.941 14 16.941 L 20 16.941 C 20.55 16.941 21 16.491 21 15.941 L 21 5.941 C 21 5.391 20.55 4.941 20 4.941 Z M 15 13.941 L 19 13.941 L 19 6.941 L 15 6.941 L 15 13.941 Z"
+ android:fillColor="#5f6368"
+ android:strokeWidth="1"
+ android:fillType="evenOdd"/>
+</vector> \ No newline at end of file
diff --git a/packages/CredentialManager/res/drawable/ic_passkeys_onboarding_fingerprint.xml b/packages/CredentialManager/res/drawable/ic_passkeys_onboarding_fingerprint.xml
new file mode 100644
index 000000000000..b6ee4f9a1869
--- /dev/null
+++ b/packages/CredentialManager/res/drawable/ic_passkeys_onboarding_fingerprint.xml
@@ -0,0 +1,31 @@
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<vector
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ tools:ignore="VectorPath"
+ android:name="vector"
+ android:width="19dp"
+ android:height="21dp"
+ android:viewportWidth="19"
+ android:viewportHeight="21">
+ <path
+ android:name="path"
+ android:pathData="M 0.25 8.591 C 0.133 8.508 0.058 8.408 0.025 8.291 C 0.008 8.158 0.05 8.025 0.15 7.891 C 1.183 6.475 2.475 5.375 4.025 4.591 C 5.592 3.808 7.258 3.416 9.025 3.416 C 10.792 3.416 12.458 3.8 14.025 4.566 C 15.592 5.316 16.9 6.408 17.95 7.841 C 18.067 7.991 18.1 8.125 18.05 8.241 C 18.017 8.358 17.95 8.458 17.85 8.541 C 17.75 8.625 17.633 8.666 17.5 8.666 C 17.367 8.65 17.25 8.575 17.15 8.441 C 16.233 7.141 15.05 6.15 13.6 5.466 C 12.167 4.766 10.642 4.416 9.025 4.416 C 7.408 4.416 5.892 4.766 4.475 5.466 C 3.058 6.15 1.883 7.141 0.95 8.441 C 0.85 8.591 0.733 8.675 0.6 8.691 C 0.467 8.708 0.35 8.675 0.25 8.591 Z M 11.85 20.916 C 10.117 20.483 8.7 19.625 7.6 18.341 C 6.5 17.041 5.95 15.458 5.95 13.591 C 5.95 12.758 6.25 12.058 6.85 11.491 C 7.45 10.925 8.175 10.641 9.025 10.641 C 9.875 10.641 10.6 10.925 11.2 11.491 C 11.8 12.058 12.1 12.758 12.1 13.591 C 12.1 14.141 12.308 14.608 12.725 14.991 C 13.142 15.358 13.633 15.541 14.2 15.541 C 14.767 15.541 15.25 15.358 15.65 14.991 C 16.05 14.608 16.25 14.141 16.25 13.591 C 16.25 11.658 15.542 10.033 14.125 8.716 C 12.708 7.4 11.017 6.741 9.05 6.741 C 7.083 6.741 5.392 7.4 3.975 8.716 C 2.558 10.033 1.85 11.65 1.85 13.566 C 1.85 13.966 1.883 14.466 1.95 15.066 C 2.033 15.666 2.217 16.366 2.5 17.166 C 2.55 17.316 2.542 17.45 2.475 17.566 C 2.425 17.683 2.333 17.766 2.2 17.816 C 2.067 17.866 1.933 17.866 1.8 17.816 C 1.683 17.75 1.6 17.65 1.55 17.516 C 1.3 16.866 1.117 16.225 1 15.591 C 0.9 14.941 0.85 14.275 0.85 13.591 C 0.85 11.375 1.65 9.516 3.25 8.016 C 4.867 6.516 6.792 5.766 9.025 5.766 C 11.275 5.766 13.208 6.516 14.825 8.016 C 16.442 9.516 17.25 11.375 17.25 13.591 C 17.25 14.425 16.95 15.125 16.35 15.691 C 15.767 16.241 15.05 16.516 14.2 16.516 C 13.35 16.516 12.617 16.241 12 15.691 C 11.4 15.125 11.1 14.425 11.1 13.591 C 11.1 13.041 10.892 12.583 10.475 12.216 C 10.075 11.833 9.592 11.641 9.025 11.641 C 8.458 11.641 7.967 11.833 7.55 12.216 C 7.15 12.583 6.95 13.041 6.95 13.591 C 6.95 15.208 7.425 16.558 8.375 17.641 C 9.342 18.725 10.583 19.483 12.1 19.916 C 12.25 19.966 12.35 20.05 12.4 20.166 C 12.45 20.283 12.458 20.408 12.425 20.541 C 12.392 20.658 12.325 20.758 12.225 20.841 C 12.125 20.925 12 20.95 11.85 20.916 Z M 3.5 3.366 C 3.367 3.45 3.233 3.475 3.1 3.441 C 2.967 3.391 2.867 3.3 2.8 3.166 C 2.733 3.033 2.717 2.916 2.75 2.816 C 2.783 2.7 2.867 2.6 3 2.516 C 3.933 2.016 4.908 1.633 5.925 1.366 C 6.942 1.1 7.975 0.966 9.025 0.966 C 10.092 0.966 11.133 1.1 12.15 1.366 C 13.167 1.616 14.15 1.983 15.1 2.466 C 15.25 2.55 15.333 2.65 15.35 2.766 C 15.383 2.883 15.375 3 15.325 3.116 C 15.275 3.233 15.192 3.325 15.075 3.391 C 14.958 3.458 14.817 3.45 14.65 3.366 C 13.767 2.916 12.85 2.575 11.9 2.341 C 10.967 2.091 10.008 1.966 9.025 1.966 C 8.058 1.966 7.108 2.083 6.175 2.316 C 5.242 2.533 4.35 2.883 3.5 3.366 Z M 6.45 20.566 C 5.467 19.533 4.708 18.483 4.175 17.416 C 3.658 16.333 3.4 15.058 3.4 13.591 C 3.4 12.075 3.95 10.8 5.05 9.766 C 6.15 8.716 7.475 8.191 9.025 8.191 C 10.575 8.191 11.908 8.716 13.025 9.766 C 14.142 10.8 14.7 12.075 14.7 13.591 C 14.7 13.741 14.65 13.866 14.55 13.966 C 14.467 14.05 14.35 14.091 14.2 14.091 C 14.067 14.091 13.95 14.05 13.85 13.966 C 13.75 13.866 13.7 13.741 13.7 13.591 C 13.7 12.341 13.233 11.3 12.3 10.466 C 11.383 9.616 10.292 9.191 9.025 9.191 C 7.758 9.191 6.667 9.616 5.75 10.466 C 4.85 11.3 4.4 12.341 4.4 13.591 C 4.4 14.941 4.633 16.091 5.1 17.041 C 5.567 17.975 6.25 18.916 7.15 19.866 C 7.25 19.966 7.3 20.083 7.3 20.216 C 7.3 20.35 7.25 20.466 7.15 20.566 C 7.05 20.666 6.933 20.716 6.8 20.716 C 6.667 20.716 6.55 20.666 6.45 20.566 Z M 14 18.866 C 12.517 18.866 11.225 18.366 10.125 17.366 C 9.042 16.366 8.5 15.108 8.5 13.591 C 8.5 13.458 8.542 13.341 8.625 13.241 C 8.725 13.141 8.85 13.091 9 13.091 C 9.15 13.091 9.267 13.141 9.35 13.241 C 9.45 13.341 9.5 13.458 9.5 13.591 C 9.5 14.841 9.95 15.866 10.85 16.666 C 11.75 17.466 12.8 17.866 14 17.866 C 14.1 17.866 14.242 17.858 14.425 17.841 C 14.608 17.825 14.8 17.8 15 17.766 C 15.15 17.733 15.275 17.758 15.375 17.841 C 15.492 17.908 15.567 18.016 15.6 18.166 C 15.633 18.3 15.608 18.416 15.525 18.516 C 15.442 18.616 15.333 18.683 15.2 18.716 C 14.9 18.8 14.633 18.85 14.4 18.866 L 14 18.866 Z"
+ android:fillColor="#5e6144"
+ android:strokeWidth="1"/>
+</vector> \ No newline at end of file
diff --git a/packages/CredentialManager/res/drawable/ic_passkeys_onboarding_password.xml b/packages/CredentialManager/res/drawable/ic_passkeys_onboarding_password.xml
new file mode 100644
index 000000000000..61800f1495d2
--- /dev/null
+++ b/packages/CredentialManager/res/drawable/ic_passkeys_onboarding_password.xml
@@ -0,0 +1,31 @@
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<vector
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ tools:ignore="VectorPath"
+ android:name="vector"
+ android:width="20dp"
+ android:height="17dp"
+ android:viewportWidth="20"
+ android:viewportHeight="17">
+ <path
+ android:name="path"
+ android:pathData="M 2 16.941 C 1.45 16.941 0.975 16.75 0.575 16.366 C 0.192 15.966 0 15.491 0 14.941 L 0 2.941 C 0 2.391 0.192 1.925 0.575 1.541 C 0.975 1.141 1.45 0.941 2 0.941 L 18 0.941 C 18.55 0.941 19.017 1.141 19.4 1.541 C 19.8 1.925 20 2.391 20 2.941 L 20 14.941 C 20 15.491 19.8 15.966 19.4 16.366 C 19.017 16.75 18.55 16.941 18 16.941 L 2 16.941 Z M 4.5 11.941 L 5.65 11.941 L 5.65 5.941 L 4.75 5.941 L 3 7.191 L 3.6 8.091 L 4.5 7.441 L 4.5 11.941 Z M 7.6 11.941 L 11.5 11.941 L 11.5 10.941 L 9.15 10.941 L 9.1 10.891 C 9.45 10.558 9.733 10.275 9.95 10.041 C 10.183 9.808 10.367 9.625 10.5 9.491 C 10.8 9.191 11.025 8.891 11.175 8.591 C 11.325 8.291 11.4 7.975 11.4 7.641 C 11.4 7.158 11.217 6.758 10.85 6.441 C 10.483 6.108 10.017 5.941 9.45 5.941 C 9.017 5.941 8.625 6.066 8.275 6.316 C 7.925 6.566 7.683 6.891 7.55 7.291 L 8.55 7.691 C 8.633 7.475 8.75 7.308 8.9 7.191 C 9.067 7.058 9.25 6.991 9.45 6.991 C 9.7 6.991 9.9 7.058 10.05 7.191 C 10.217 7.325 10.3 7.491 10.3 7.691 C 10.3 7.875 10.267 8.05 10.2 8.216 C 10.133 8.366 9.983 8.558 9.75 8.791 L 8.95 9.591 C 8.6 9.941 8.15 10.391 7.6 10.941 L 7.6 11.941 Z M 15 11.941 C 15.6 11.941 16.083 11.775 16.45 11.441 C 16.817 11.108 17 10.675 17 10.141 C 17 9.841 16.917 9.575 16.75 9.341 C 16.583 9.108 16.35 8.925 16.05 8.791 L 16.05 8.741 C 16.283 8.608 16.467 8.441 16.6 8.241 C 16.733 8.025 16.8 7.775 16.8 7.491 C 16.8 7.041 16.625 6.675 16.275 6.391 C 15.925 6.091 15.483 5.941 14.95 5.941 C 14.533 5.941 14.142 6.066 13.775 6.316 C 13.425 6.55 13.2 6.841 13.1 7.191 L 14.1 7.591 C 14.167 7.391 14.275 7.233 14.425 7.116 C 14.575 7 14.75 6.941 14.95 6.941 C 15.167 6.941 15.342 7.008 15.475 7.141 C 15.625 7.258 15.7 7.408 15.7 7.591 C 15.7 7.825 15.617 8.008 15.45 8.141 C 15.283 8.275 15.067 8.341 14.8 8.341 L 14.35 8.341 L 14.35 9.341 L 14.85 9.341 C 15.183 9.341 15.442 9.408 15.625 9.541 C 15.808 9.675 15.9 9.858 15.9 10.091 C 15.9 10.308 15.808 10.5 15.625 10.666 C 15.442 10.816 15.233 10.891 15 10.891 C 14.717 10.891 14.5 10.833 14.35 10.716 C 14.2 10.583 14.067 10.358 13.95 10.041 L 12.95 10.441 C 13.067 10.925 13.3 11.3 13.65 11.566 C 14.017 11.816 14.467 11.941 15 11.941 Z M 2 14.941 L 18 14.941 L 18 2.941 L 2 2.941 L 2 14.941 Z M 2 14.941 L 2 2.941 L 2 14.941 Z"
+ android:fillColor="#5e6144"
+ android:strokeWidth="1"/>
+</vector> \ No newline at end of file
diff --git a/packages/CredentialManager/res/values-af/strings.xml b/packages/CredentialManager/res/values-af/strings.xml
index 91771b3ab287..377c13fb7881 100644
--- a/packages/CredentialManager/res/values-af/strings.xml
+++ b/packages/CredentialManager/res/values-af/strings.xml
@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for app_name (4539824758261855508) -->
- <skip />
+ <string name="app_name" msgid="4539824758261855508">"Eiebewysbestuurder"</string>
<string name="string_cancel" msgid="6369133483981306063">"Kanselleer"</string>
<string name="string_continue" msgid="1346732695941131882">"Gaan voort"</string>
<string name="string_create_in_another_place" msgid="1033635365843437603">"Skep op ’n ander plek"</string>
@@ -31,6 +30,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Gebruik <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> vir al jou aanmeldings?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
@@ -39,8 +40,7 @@
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> wagwoorde, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> wagwoordsleutels"</string>
<string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> wagwoorde"</string>
<string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> wagwoordsleutels"</string>
- <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
- <skip />
+ <string name="passkey_before_subtitle" msgid="2448119456208647444">"Wagwoordsleutel"</string>
<string name="another_device" msgid="5147276802037801217">"’n Ander toestel"</string>
<string name="other_password_manager" msgid="565790221427004141">"Ander wagwoordbestuurders"</string>
<string name="close_sheet" msgid="1393792015338908262">"Maak sigblad toe"</string>
diff --git a/packages/CredentialManager/res/values-am/strings.xml b/packages/CredentialManager/res/values-am/strings.xml
index e77b1a788ea4..b80fe2cee53a 100644
--- a/packages/CredentialManager/res/values-am/strings.xml
+++ b/packages/CredentialManager/res/values-am/strings.xml
@@ -48,6 +48,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<!-- no translation found for use_provider_for_all_title (4201020195058980757) -->
<skip />
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
diff --git a/packages/CredentialManager/res/values-ar/strings.xml b/packages/CredentialManager/res/values-ar/strings.xml
index 4af875ce3519..a5c85c5281b2 100644
--- a/packages/CredentialManager/res/values-ar/strings.xml
+++ b/packages/CredentialManager/res/values-ar/strings.xml
@@ -8,8 +8,7 @@
<string name="string_create_in_another_place" msgid="1033635365843437603">"الإنشاء في مكان آخر"</string>
<string name="string_save_to_another_place" msgid="7590325934591079193">"الحفظ في مكان آخر"</string>
<string name="string_use_another_device" msgid="8754514926121520445">"استخدام جهاز آخر"</string>
- <!-- no translation found for string_save_to_another_device (1959562542075194458) -->
- <skip />
+ <string name="string_save_to_another_device" msgid="1959562542075194458">"الحفظ على جهاز آخر"</string>
<string name="passkey_creation_intro_title" msgid="402553911484409884">"طريقة بسيطة لتسجيل الدخول بأمان"</string>
<string name="passkey_creation_intro_body" msgid="7493320456005579290">"استخدِم بصمة إصبعك أو وجهك أو قفل الشاشة لتسجيل الدخول باستخدام مفتاح مرور فريد لا يمكن نسيانه أو سرقته. مزيد من المعلومات"</string>
<string name="choose_provider_title" msgid="7245243990139698508">"اختيار مكان <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
@@ -22,7 +21,7 @@
<string name="choose_create_option_passkey_title" msgid="4146408187146573131">"هل تريد إنشاء مفتاح مرور في \"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>\"؟"</string>
<string name="choose_create_option_password_title" msgid="8812546498357380545">"هل تريد حفظ كلمة مرورك في \"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>\"؟"</string>
<string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"هل تريد حفظ معلومات تسجيل الدخول في \"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>\"؟"</string>
- <string name="choose_create_option_description" msgid="4419171903963100257">"يمكنك استخدام <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> على أي جهاز. يتم حفظه في \"<xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g>\" لـ \"<xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>\"."</string>
+ <string name="choose_create_option_description" msgid="4419171903963100257">"يمكنك استخدام <xliff:g id="TYPE">%2$s</xliff:g> الخاص بـ \"<xliff:g id="APPDOMAINNAME">%1$s</xliff:g>\" على أي جهاز. ويتم حفظه في \"<xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g>\" للحساب \"<xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>\"."</string>
<string name="passkey" msgid="632353688396759522">"مفتاح مرور"</string>
<string name="password" msgid="6738570945182936667">"كلمة المرور"</string>
<string name="sign_ins" msgid="4710739369149469208">"عمليات تسجيل الدخول"</string>
@@ -32,6 +31,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"هل تريد استخدام \"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>\" لكل عمليات تسجيل الدخول؟"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
diff --git a/packages/CredentialManager/res/values-as/strings.xml b/packages/CredentialManager/res/values-as/strings.xml
index c1040987b3d3..4d0ba6815947 100644
--- a/packages/CredentialManager/res/values-as/strings.xml
+++ b/packages/CredentialManager/res/values-as/strings.xml
@@ -1,15 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for app_name (4539824758261855508) -->
- <skip />
+ <string name="app_name" msgid="4539824758261855508">"CredentialManager"</string>
<string name="string_cancel" msgid="6369133483981306063">"বাতিল কৰক"</string>
<string name="string_continue" msgid="1346732695941131882">"অব্যাহত ৰাখক"</string>
<string name="string_create_in_another_place" msgid="1033635365843437603">"অন্য ঠাইত সৃষ্টি কৰক"</string>
<string name="string_save_to_another_place" msgid="7590325934591079193">"অন্য ঠাইত ছেভ কৰক"</string>
<string name="string_use_another_device" msgid="8754514926121520445">"অন্য ডিভাইচ ব্যৱহাৰ কৰক"</string>
- <!-- no translation found for string_save_to_another_device (1959562542075194458) -->
- <skip />
+ <string name="string_save_to_another_device" msgid="1959562542075194458">"অন্য এটা ডিভাইচত ছেভ কৰক"</string>
<string name="passkey_creation_intro_title" msgid="402553911484409884">"সুৰক্ষিতভাৱে ছাইন ইন কৰাৰ এক সৰল উপায়"</string>
<string name="passkey_creation_intro_body" msgid="7493320456005579290">"পাহৰি নোযোৱা অথবা চুৰি কৰিব নোৱৰা এটা অদ্বিতীয় পাছকী ব্যৱহাৰ কৰি ছাইন ইন কৰিবলৈ আপোনাৰ ফিংগাৰপ্ৰিণ্ট, মুখাৱয়ব অথবা স্ক্ৰীন লক ব্যৱহাৰ কৰক। অধিক জানক"</string>
<string name="choose_provider_title" msgid="7245243990139698508">"ক’ত <xliff:g id="CREATETYPES">%1$s</xliff:g> সেয়া বাছনি কৰক"</string>
@@ -32,6 +30,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"আপোনাৰ আটাইবোৰ ছাইন ইনৰ বাবে <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ব্যৱহাৰ কৰিবনে?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
@@ -40,8 +40,7 @@
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> টা পাছৱৰ্ড, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> টা পাছকী"</string>
<string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> টা পাছৱৰ্ড"</string>
<string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> টা পাছকী"</string>
- <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
- <skip />
+ <string name="passkey_before_subtitle" msgid="2448119456208647444">"পাছকী"</string>
<string name="another_device" msgid="5147276802037801217">"অন্য এটা ডিভাইচ"</string>
<string name="other_password_manager" msgid="565790221427004141">"অন্য পাছৱৰ্ড পৰিচালক"</string>
<string name="close_sheet" msgid="1393792015338908262">"শ্বীট বন্ধ কৰক"</string>
diff --git a/packages/CredentialManager/res/values-az/strings.xml b/packages/CredentialManager/res/values-az/strings.xml
index 4a8fef514785..14313f759b48 100644
--- a/packages/CredentialManager/res/values-az/strings.xml
+++ b/packages/CredentialManager/res/values-az/strings.xml
@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for app_name (4539824758261855508) -->
- <skip />
+ <string name="app_name" msgid="4539824758261855508">"Giriş Məlumatları Meneceri"</string>
<string name="string_cancel" msgid="6369133483981306063">"Ləğv edin"</string>
<string name="string_continue" msgid="1346732695941131882">"Davam edin"</string>
<string name="string_create_in_another_place" msgid="1033635365843437603">"Başqa yerdə yaradın"</string>
@@ -31,6 +30,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Bütün girişlər üçün <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> istifadə edilsin?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
@@ -39,8 +40,7 @@
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> parol, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> giriş açarı"</string>
<string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> parol"</string>
<string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> giriş açarı"</string>
- <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
- <skip />
+ <string name="passkey_before_subtitle" msgid="2448119456208647444">"Giriş açarı"</string>
<string name="another_device" msgid="5147276802037801217">"Digər cihaz"</string>
<string name="other_password_manager" msgid="565790221427004141">"Digər parol menecerləri"</string>
<string name="close_sheet" msgid="1393792015338908262">"Səhifəni bağlayın"</string>
diff --git a/packages/CredentialManager/res/values-b+sr+Latn/strings.xml b/packages/CredentialManager/res/values-b+sr+Latn/strings.xml
index b46518eafef4..c58ec141f01b 100644
--- a/packages/CredentialManager/res/values-b+sr+Latn/strings.xml
+++ b/packages/CredentialManager/res/values-b+sr+Latn/strings.xml
@@ -31,6 +31,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Želite da za sva prijavljivanja koristite: <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
diff --git a/packages/CredentialManager/res/values-be/strings.xml b/packages/CredentialManager/res/values-be/strings.xml
index afa4d015f918..3c23afd5101b 100644
--- a/packages/CredentialManager/res/values-be/strings.xml
+++ b/packages/CredentialManager/res/values-be/strings.xml
@@ -31,6 +31,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Выкарыстоўваць папку \"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>\" для ўсіх спосабаў уваходу?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
diff --git a/packages/CredentialManager/res/values-bg/strings.xml b/packages/CredentialManager/res/values-bg/strings.xml
index 1a2f881f6bc5..af7eb172810a 100644
--- a/packages/CredentialManager/res/values-bg/strings.xml
+++ b/packages/CredentialManager/res/values-bg/strings.xml
@@ -31,6 +31,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Да се използва ли <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> за всичките ви данни за вход?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
diff --git a/packages/CredentialManager/res/values-bn/strings.xml b/packages/CredentialManager/res/values-bn/strings.xml
index 3257b7837c4b..152e5bd8e852 100644
--- a/packages/CredentialManager/res/values-bn/strings.xml
+++ b/packages/CredentialManager/res/values-bn/strings.xml
@@ -31,6 +31,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"আপনার সব সাইন-ইনের জন্য <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ব্যবহার করবেন?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
diff --git a/packages/CredentialManager/res/values-bs/strings.xml b/packages/CredentialManager/res/values-bs/strings.xml
index 705cfefcf979..d774b8810f21 100644
--- a/packages/CredentialManager/res/values-bs/strings.xml
+++ b/packages/CredentialManager/res/values-bs/strings.xml
@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for app_name (4539824758261855508) -->
- <skip />
+ <string name="app_name" msgid="4539824758261855508">"Upravitelj akreditiva"</string>
<string name="string_cancel" msgid="6369133483981306063">"Otkaži"</string>
<string name="string_continue" msgid="1346732695941131882">"Nastavi"</string>
<string name="string_create_in_another_place" msgid="1033635365843437603">"Kreirajte na drugom mjestu"</string>
@@ -31,6 +30,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Koristiti uslugu <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> za sve vaše prijave?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
@@ -39,8 +40,7 @@
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"Broj lozinki: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>; broj pristupnih ključeva: <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>"</string>
<string name="more_options_usage_passwords" msgid="1632047277723187813">"Broj lozinki: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>"</string>
<string name="more_options_usage_passkeys" msgid="5390320437243042237">"Broj pristupnih ključeva: <xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g>"</string>
- <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
- <skip />
+ <string name="passkey_before_subtitle" msgid="2448119456208647444">"Pristupni ključ"</string>
<string name="another_device" msgid="5147276802037801217">"Drugi uređaj"</string>
<string name="other_password_manager" msgid="565790221427004141">"Drugi upravitelji lozinki"</string>
<string name="close_sheet" msgid="1393792015338908262">"Zatvaranje tabele"</string>
diff --git a/packages/CredentialManager/res/values-ca/strings.xml b/packages/CredentialManager/res/values-ca/strings.xml
index d9b5a9039d92..bfd91647bd9c 100644
--- a/packages/CredentialManager/res/values-ca/strings.xml
+++ b/packages/CredentialManager/res/values-ca/strings.xml
@@ -8,8 +8,7 @@
<string name="string_create_in_another_place" msgid="1033635365843437603">"Crea en un altre lloc"</string>
<string name="string_save_to_another_place" msgid="7590325934591079193">"Desa en un altre lloc"</string>
<string name="string_use_another_device" msgid="8754514926121520445">"Utilitza un altre dispositiu"</string>
- <!-- no translation found for string_save_to_another_device (1959562542075194458) -->
- <skip />
+ <string name="string_save_to_another_device" msgid="1959562542075194458">"Desa en un altre dispositiu"</string>
<string name="passkey_creation_intro_title" msgid="402553911484409884">"Una manera senzilla i segura d\'iniciar la sessió"</string>
<string name="passkey_creation_intro_body" msgid="7493320456005579290">"Utilitza l\'empremta digital, la cara o el bloqueig de pantalla per iniciar la sessió amb una clau d\'accés única que no es pot oblidar ni robar. Més informació"</string>
<string name="choose_provider_title" msgid="7245243990139698508">"Tria on vols <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
@@ -32,6 +31,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Vols utilitzar <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> per a tots els teus inicis de sessió?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
diff --git a/packages/CredentialManager/res/values-cs/strings.xml b/packages/CredentialManager/res/values-cs/strings.xml
index e7fe5365ff26..72a5f98e9ad4 100644
--- a/packages/CredentialManager/res/values-cs/strings.xml
+++ b/packages/CredentialManager/res/values-cs/strings.xml
@@ -31,6 +31,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Používat <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> pro všechna přihlášení?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
diff --git a/packages/CredentialManager/res/values-da/strings.xml b/packages/CredentialManager/res/values-da/strings.xml
index 86cf9ff210d2..a05137e1401f 100644
--- a/packages/CredentialManager/res/values-da/strings.xml
+++ b/packages/CredentialManager/res/values-da/strings.xml
@@ -31,6 +31,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Vil du bruge <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> til alle dine loginmetoder?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
diff --git a/packages/CredentialManager/res/values-de/strings.xml b/packages/CredentialManager/res/values-de/strings.xml
index d239dd3d2664..fa1e8f10f87a 100644
--- a/packages/CredentialManager/res/values-de/strings.xml
+++ b/packages/CredentialManager/res/values-de/strings.xml
@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for app_name (4539824758261855508) -->
- <skip />
+ <string name="app_name" msgid="4539824758261855508">"Anmeldedaten-Manager"</string>
<string name="string_cancel" msgid="6369133483981306063">"Abbrechen"</string>
<string name="string_continue" msgid="1346732695941131882">"Weiter"</string>
<string name="string_create_in_another_place" msgid="1033635365843437603">"An anderem Speicherort erstellen"</string>
@@ -31,6 +30,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> für alle Anmeldungen verwenden?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
@@ -39,8 +40,7 @@
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> Passwörter, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> Passkeys"</string>
<string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> Passwörter"</string>
<string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> Passkeys"</string>
- <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
- <skip />
+ <string name="passkey_before_subtitle" msgid="2448119456208647444">"Passkey"</string>
<string name="another_device" msgid="5147276802037801217">"Ein anderes Gerät"</string>
<string name="other_password_manager" msgid="565790221427004141">"Andere Passwortmanager"</string>
<string name="close_sheet" msgid="1393792015338908262">"Tabellenblatt schließen"</string>
diff --git a/packages/CredentialManager/res/values-el/strings.xml b/packages/CredentialManager/res/values-el/strings.xml
index 9b7ccbb24ad1..706d9f3c4c7a 100644
--- a/packages/CredentialManager/res/values-el/strings.xml
+++ b/packages/CredentialManager/res/values-el/strings.xml
@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for app_name (4539824758261855508) -->
- <skip />
+ <string name="app_name" msgid="4539824758261855508">"Credential Manager"</string>
<string name="string_cancel" msgid="6369133483981306063">"Ακύρωση"</string>
<string name="string_continue" msgid="1346732695941131882">"Συνέχεια"</string>
<string name="string_create_in_another_place" msgid="1033635365843437603">"Δημιουργία σε άλλη θέση"</string>
@@ -31,6 +30,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Να χρησιμοποιηθεί το <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> για όλες τις συνδέσεις σας;"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
@@ -39,8 +40,7 @@
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> κωδικοί πρόσβασης, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> κλειδιά πρόσβασης"</string>
<string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> κωδικοί πρόσβασης"</string>
<string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> κλειδιά πρόσβασης"</string>
- <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
- <skip />
+ <string name="passkey_before_subtitle" msgid="2448119456208647444">"Κλειδί πρόσβασης"</string>
<string name="another_device" msgid="5147276802037801217">"Άλλη συσκευή"</string>
<string name="other_password_manager" msgid="565790221427004141">"Άλλοι διαχειριστές κωδικών πρόσβασης"</string>
<string name="close_sheet" msgid="1393792015338908262">"Κλείσιμο φύλλου"</string>
diff --git a/packages/CredentialManager/res/values-en-rAU/strings.xml b/packages/CredentialManager/res/values-en-rAU/strings.xml
index 682dffbf4e28..7adeded11424 100644
--- a/packages/CredentialManager/res/values-en-rAU/strings.xml
+++ b/packages/CredentialManager/res/values-en-rAU/strings.xml
@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for app_name (4539824758261855508) -->
- <skip />
+ <string name="app_name" msgid="4539824758261855508">"Credential manager"</string>
<string name="string_cancel" msgid="6369133483981306063">"Cancel"</string>
<string name="string_continue" msgid="1346732695941131882">"Continue"</string>
<string name="string_create_in_another_place" msgid="1033635365843437603">"Create in another place"</string>
@@ -12,12 +11,10 @@
<string name="passkey_creation_intro_title" msgid="402553911484409884">"A simple way to sign in safely"</string>
<string name="passkey_creation_intro_body" msgid="7493320456005579290">"Use your fingerprint, face or screen lock to sign in with a unique passkey that can’t be forgotten or stolen. Learn more"</string>
<string name="choose_provider_title" msgid="7245243990139698508">"Choose where to <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
- <!-- no translation found for create_your_passkeys (8901224153607590596) -->
- <skip />
+ <string name="create_your_passkeys" msgid="8901224153607590596">"create your passkeys"</string>
<string name="save_your_password" msgid="6597736507991704307">"save your password"</string>
<string name="save_your_sign_in_info" msgid="7213978049817076882">"save your sign-in info"</string>
- <!-- no translation found for choose_provider_body (8045759834416308059) -->
- <skip />
+ <string name="choose_provider_body" msgid="8045759834416308059">"Set a default password manager to save your passwords and passkeys and sign in faster next time."</string>
<string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Create a passkey in <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_password_title" msgid="8812546498357380545">"Save your password to <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Save your sign-in info to <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
@@ -25,22 +22,18 @@
<string name="passkey" msgid="632353688396759522">"passkey"</string>
<string name="password" msgid="6738570945182936667">"password"</string>
<string name="sign_ins" msgid="4710739369149469208">"sign-ins"</string>
- <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
- <skip />
- <!-- no translation found for save_password_to_title (3450480045270186421) -->
- <skip />
- <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
- <skip />
+ <string name="create_passkey_in_title" msgid="2714306562710897785">"Create passkey in"</string>
+ <string name="save_password_to_title" msgid="3450480045270186421">"Save password to"</string>
+ <string name="save_sign_in_to_title" msgid="8328143607671760232">"Save sign-in to"</string>
+ <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"Create a passkey in another device?"</string>
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Use <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> for all your sign-ins?"</string>
- <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
- <skip />
+ <string name="use_provider_for_all_description" msgid="6560593199974037820">"This password manager will store your passwords and passkeys to help you easily sign in."</string>
<string name="set_as_default" msgid="4415328591568654603">"Set as default"</string>
<string name="use_once" msgid="9027366575315399714">"Use once"</string>
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> passwords, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> passkeys"</string>
<string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> passwords"</string>
<string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> passkeys"</string>
- <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
- <skip />
+ <string name="passkey_before_subtitle" msgid="2448119456208647444">"Passkey"</string>
<string name="another_device" msgid="5147276802037801217">"Another device"</string>
<string name="other_password_manager" msgid="565790221427004141">"Other password managers"</string>
<string name="close_sheet" msgid="1393792015338908262">"Close sheet"</string>
diff --git a/packages/CredentialManager/res/values-en-rCA/strings.xml b/packages/CredentialManager/res/values-en-rCA/strings.xml
index 4ec2872f7c0b..8a8b8845f4cf 100644
--- a/packages/CredentialManager/res/values-en-rCA/strings.xml
+++ b/packages/CredentialManager/res/values-en-rCA/strings.xml
@@ -11,12 +11,10 @@
<string name="passkey_creation_intro_title" msgid="402553911484409884">"A simple way to sign in safely"</string>
<string name="passkey_creation_intro_body" msgid="7493320456005579290">"Use your fingerprint, face or screen lock to sign in with a unique passkey that can’t be forgotten or stolen. Learn more"</string>
<string name="choose_provider_title" msgid="7245243990139698508">"Choose where to <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
- <!-- no translation found for create_your_passkeys (8901224153607590596) -->
- <skip />
+ <string name="create_your_passkeys" msgid="8901224153607590596">"create your passkeys"</string>
<string name="save_your_password" msgid="6597736507991704307">"save your password"</string>
<string name="save_your_sign_in_info" msgid="7213978049817076882">"save your sign-in info"</string>
- <!-- no translation found for choose_provider_body (8045759834416308059) -->
- <skip />
+ <string name="choose_provider_body" msgid="8045759834416308059">"Set a default password manager to save your passwords and passkeys and sign in faster next time."</string>
<string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Create a passkey in <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_password_title" msgid="8812546498357380545">"Save your password to <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Save your sign-in info to <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
@@ -24,15 +22,12 @@
<string name="passkey" msgid="632353688396759522">"passkey"</string>
<string name="password" msgid="6738570945182936667">"password"</string>
<string name="sign_ins" msgid="4710739369149469208">"sign-ins"</string>
- <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
- <skip />
- <!-- no translation found for save_password_to_title (3450480045270186421) -->
- <skip />
- <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
- <skip />
+ <string name="create_passkey_in_title" msgid="2714306562710897785">"Create passkey in"</string>
+ <string name="save_password_to_title" msgid="3450480045270186421">"Save password to"</string>
+ <string name="save_sign_in_to_title" msgid="8328143607671760232">"Save sign-in to"</string>
+ <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"Create a passkey in another device?"</string>
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Use <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> for all your sign-ins?"</string>
- <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
- <skip />
+ <string name="use_provider_for_all_description" msgid="6560593199974037820">"This password manager will store your passwords and passkeys to help you easily sign in."</string>
<string name="set_as_default" msgid="4415328591568654603">"Set as default"</string>
<string name="use_once" msgid="9027366575315399714">"Use once"</string>
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> passwords, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> passkeys"</string>
diff --git a/packages/CredentialManager/res/values-en-rGB/strings.xml b/packages/CredentialManager/res/values-en-rGB/strings.xml
index 682dffbf4e28..7adeded11424 100644
--- a/packages/CredentialManager/res/values-en-rGB/strings.xml
+++ b/packages/CredentialManager/res/values-en-rGB/strings.xml
@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for app_name (4539824758261855508) -->
- <skip />
+ <string name="app_name" msgid="4539824758261855508">"Credential manager"</string>
<string name="string_cancel" msgid="6369133483981306063">"Cancel"</string>
<string name="string_continue" msgid="1346732695941131882">"Continue"</string>
<string name="string_create_in_another_place" msgid="1033635365843437603">"Create in another place"</string>
@@ -12,12 +11,10 @@
<string name="passkey_creation_intro_title" msgid="402553911484409884">"A simple way to sign in safely"</string>
<string name="passkey_creation_intro_body" msgid="7493320456005579290">"Use your fingerprint, face or screen lock to sign in with a unique passkey that can’t be forgotten or stolen. Learn more"</string>
<string name="choose_provider_title" msgid="7245243990139698508">"Choose where to <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
- <!-- no translation found for create_your_passkeys (8901224153607590596) -->
- <skip />
+ <string name="create_your_passkeys" msgid="8901224153607590596">"create your passkeys"</string>
<string name="save_your_password" msgid="6597736507991704307">"save your password"</string>
<string name="save_your_sign_in_info" msgid="7213978049817076882">"save your sign-in info"</string>
- <!-- no translation found for choose_provider_body (8045759834416308059) -->
- <skip />
+ <string name="choose_provider_body" msgid="8045759834416308059">"Set a default password manager to save your passwords and passkeys and sign in faster next time."</string>
<string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Create a passkey in <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_password_title" msgid="8812546498357380545">"Save your password to <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Save your sign-in info to <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
@@ -25,22 +22,18 @@
<string name="passkey" msgid="632353688396759522">"passkey"</string>
<string name="password" msgid="6738570945182936667">"password"</string>
<string name="sign_ins" msgid="4710739369149469208">"sign-ins"</string>
- <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
- <skip />
- <!-- no translation found for save_password_to_title (3450480045270186421) -->
- <skip />
- <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
- <skip />
+ <string name="create_passkey_in_title" msgid="2714306562710897785">"Create passkey in"</string>
+ <string name="save_password_to_title" msgid="3450480045270186421">"Save password to"</string>
+ <string name="save_sign_in_to_title" msgid="8328143607671760232">"Save sign-in to"</string>
+ <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"Create a passkey in another device?"</string>
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Use <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> for all your sign-ins?"</string>
- <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
- <skip />
+ <string name="use_provider_for_all_description" msgid="6560593199974037820">"This password manager will store your passwords and passkeys to help you easily sign in."</string>
<string name="set_as_default" msgid="4415328591568654603">"Set as default"</string>
<string name="use_once" msgid="9027366575315399714">"Use once"</string>
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> passwords, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> passkeys"</string>
<string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> passwords"</string>
<string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> passkeys"</string>
- <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
- <skip />
+ <string name="passkey_before_subtitle" msgid="2448119456208647444">"Passkey"</string>
<string name="another_device" msgid="5147276802037801217">"Another device"</string>
<string name="other_password_manager" msgid="565790221427004141">"Other password managers"</string>
<string name="close_sheet" msgid="1393792015338908262">"Close sheet"</string>
diff --git a/packages/CredentialManager/res/values-en-rIN/strings.xml b/packages/CredentialManager/res/values-en-rIN/strings.xml
index 682dffbf4e28..7adeded11424 100644
--- a/packages/CredentialManager/res/values-en-rIN/strings.xml
+++ b/packages/CredentialManager/res/values-en-rIN/strings.xml
@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for app_name (4539824758261855508) -->
- <skip />
+ <string name="app_name" msgid="4539824758261855508">"Credential manager"</string>
<string name="string_cancel" msgid="6369133483981306063">"Cancel"</string>
<string name="string_continue" msgid="1346732695941131882">"Continue"</string>
<string name="string_create_in_another_place" msgid="1033635365843437603">"Create in another place"</string>
@@ -12,12 +11,10 @@
<string name="passkey_creation_intro_title" msgid="402553911484409884">"A simple way to sign in safely"</string>
<string name="passkey_creation_intro_body" msgid="7493320456005579290">"Use your fingerprint, face or screen lock to sign in with a unique passkey that can’t be forgotten or stolen. Learn more"</string>
<string name="choose_provider_title" msgid="7245243990139698508">"Choose where to <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
- <!-- no translation found for create_your_passkeys (8901224153607590596) -->
- <skip />
+ <string name="create_your_passkeys" msgid="8901224153607590596">"create your passkeys"</string>
<string name="save_your_password" msgid="6597736507991704307">"save your password"</string>
<string name="save_your_sign_in_info" msgid="7213978049817076882">"save your sign-in info"</string>
- <!-- no translation found for choose_provider_body (8045759834416308059) -->
- <skip />
+ <string name="choose_provider_body" msgid="8045759834416308059">"Set a default password manager to save your passwords and passkeys and sign in faster next time."</string>
<string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Create a passkey in <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_password_title" msgid="8812546498357380545">"Save your password to <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Save your sign-in info to <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
@@ -25,22 +22,18 @@
<string name="passkey" msgid="632353688396759522">"passkey"</string>
<string name="password" msgid="6738570945182936667">"password"</string>
<string name="sign_ins" msgid="4710739369149469208">"sign-ins"</string>
- <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
- <skip />
- <!-- no translation found for save_password_to_title (3450480045270186421) -->
- <skip />
- <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
- <skip />
+ <string name="create_passkey_in_title" msgid="2714306562710897785">"Create passkey in"</string>
+ <string name="save_password_to_title" msgid="3450480045270186421">"Save password to"</string>
+ <string name="save_sign_in_to_title" msgid="8328143607671760232">"Save sign-in to"</string>
+ <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"Create a passkey in another device?"</string>
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Use <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> for all your sign-ins?"</string>
- <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
- <skip />
+ <string name="use_provider_for_all_description" msgid="6560593199974037820">"This password manager will store your passwords and passkeys to help you easily sign in."</string>
<string name="set_as_default" msgid="4415328591568654603">"Set as default"</string>
<string name="use_once" msgid="9027366575315399714">"Use once"</string>
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> passwords, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> passkeys"</string>
<string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> passwords"</string>
<string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> passkeys"</string>
- <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
- <skip />
+ <string name="passkey_before_subtitle" msgid="2448119456208647444">"Passkey"</string>
<string name="another_device" msgid="5147276802037801217">"Another device"</string>
<string name="other_password_manager" msgid="565790221427004141">"Other password managers"</string>
<string name="close_sheet" msgid="1393792015338908262">"Close sheet"</string>
diff --git a/packages/CredentialManager/res/values-en-rXC/strings.xml b/packages/CredentialManager/res/values-en-rXC/strings.xml
index d114a4658e64..85e94dfb60a8 100644
--- a/packages/CredentialManager/res/values-en-rXC/strings.xml
+++ b/packages/CredentialManager/res/values-en-rXC/strings.xml
@@ -11,12 +11,10 @@
<string name="passkey_creation_intro_title" msgid="402553911484409884">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‎‏‏‎‎‏‎‏‏‎‎‎‏‎‏‎‎‎‏‎‏‎‎‏‏‎‎‏‎‏‎‎‎‎‏‏‏‏‏‎‎‎‏‏‏‎‏‎‎‎‎‎‎‏‏‏‎‎‎A simple way to sign in safely‎‏‎‎‏‎"</string>
<string name="passkey_creation_intro_body" msgid="7493320456005579290">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‏‏‏‏‏‏‏‏‎‏‏‎‎‏‏‏‏‏‎‏‎‎‎‎‎‎‏‏‏‎‎‎‎‎‎‏‏‎‎‎‎‏‎‏‏‏‎‎‏‎‎‎‎‏‏‎‏‎‎Use your fingerprint, face or screen lock to sign in with a unique passkey that can’t be forgotten or stolen. Learn more‎‏‎‎‏‎"</string>
<string name="choose_provider_title" msgid="7245243990139698508">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‎‎‏‎‎‎‏‏‎‎‎‏‎‎‎‏‏‏‎‎‎‎‎‏‎‏‏‎‏‎‎‏‏‏‎‏‎‎‏‏‏‏‎‏‏‏‏‏‎‏‎‏‎‎‏‏‎‎‎Choose where to ‎‏‎‎‏‏‎<xliff:g id="CREATETYPES">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
- <!-- no translation found for create_your_passkeys (8901224153607590596) -->
- <skip />
+ <string name="create_your_passkeys" msgid="8901224153607590596">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‏‏‏‎‎‎‎‏‏‏‏‎‎‎‎‎‎‎‎‎‏‏‎‏‎‎‏‏‏‏‏‏‎‏‏‏‏‎‏‎‎‎‎‏‎‏‎‎‏‎‏‏‎‎‎‏‎‎‎create your passkeys‎‏‎‎‏‎"</string>
<string name="save_your_password" msgid="6597736507991704307">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‏‏‏‎‎‎‏‏‏‏‏‏‎‏‏‏‏‎‎‏‏‎‎‏‎‏‏‏‎‏‎‎‎‎‎‏‏‏‏‎‎‎‏‏‎‎‎‎‏‎‏‏‏‏‎‎‏‏‎save your password‎‏‎‎‏‎"</string>
<string name="save_your_sign_in_info" msgid="7213978049817076882">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‎‎‎‎‎‏‏‏‎‏‎‎‏‏‎‎‏‎‏‏‎‏‎‎‎‎‏‎‏‎‎‏‎‎‏‏‎‎‏‏‏‏‏‏‏‎‎‏‎‎‏‎‎‏‎‎‏‎‎save your sign-in info‎‏‎‎‏‎"</string>
- <!-- no translation found for choose_provider_body (8045759834416308059) -->
- <skip />
+ <string name="choose_provider_body" msgid="8045759834416308059">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‏‏‏‎‏‎‏‎‎‎‎‏‎‎‎‏‏‏‏‏‏‏‎‎‎‎‏‏‏‎‏‏‏‎‎‏‏‏‏‏‎‏‎‏‎‏‏‏‏‏‎‏‎‏‏‎‏‏‎Set a default password manager to save your passwords and passkeys and sign in faster next time.‎‏‎‎‏‎"</string>
<string name="choose_create_option_passkey_title" msgid="4146408187146573131">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‎‏‏‎‎‎‏‎‏‏‎‎‎‎‎‎‎‎‎‏‎‎‎‏‏‏‎‎‎‏‎‎‎‎‏‎‏‎‎‏‎‎‏‎‎‏‎‏‎‏‎‏‎‎‏‎‏‏‎Create a passkey in ‎‏‎‎‏‏‎<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎?‎‏‎‎‏‎"</string>
<string name="choose_create_option_password_title" msgid="8812546498357380545">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‏‎‎‏‎‎‏‏‎‎‎‏‏‏‎‏‎‎‎‏‎‏‏‎‎‏‎‏‏‏‎‎‎‏‎‎‏‏‎‏‎‎‎‏‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎Save your password to ‎‏‎‎‏‏‎<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎?‎‏‎‎‏‎"</string>
<string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‏‏‏‎‏‎‏‏‏‎‏‏‏‎‏‎‏‏‏‎‎‏‏‎‏‏‎‎‎‏‎‎‎‎‎‏‏‏‎‏‎‏‎‎‏‏‏‏‎‏‏‏‏‎‏‎‏‎‎Save your sign-in info to ‎‏‎‎‏‏‎<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎?‎‏‎‎‏‎"</string>
@@ -24,15 +22,12 @@
<string name="passkey" msgid="632353688396759522">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‎‏‏‎‎‎‏‏‎‏‎‎‏‎‎‏‎‎‏‎‏‎‏‎‎‎‏‎‎‏‎‎‏‎‏‎‎‏‏‎‎‎‎‎‎‎‎‎‏‏‏‏‎‎‎‏‎‎passkey‎‏‎‎‏‎"</string>
<string name="password" msgid="6738570945182936667">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‎‏‏‎‎‎‎‏‎‎‎‎‏‏‎‏‏‎‏‎‎‏‎‎‏‎‎‏‏‏‏‎‎‏‎‏‏‎‎‏‎‏‏‎‏‎‏‏‏‎‎‏‎‏‏‎‏‏‎password‎‏‎‎‏‎"</string>
<string name="sign_ins" msgid="4710739369149469208">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‎‏‎‏‎‏‏‏‏‏‏‏‏‎‏‎‎‎‎‏‏‏‏‏‏‏‎‏‏‎‎‏‏‏‏‎‎‏‏‎‏‎‎‎‎‏‎‎‏‎‎‎‎‏‏‎‎‎‎sign-ins‎‏‎‎‏‎"</string>
- <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
- <skip />
- <!-- no translation found for save_password_to_title (3450480045270186421) -->
- <skip />
- <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
- <skip />
+ <string name="create_passkey_in_title" msgid="2714306562710897785">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‎‏‏‎‏‎‏‎‏‏‎‎‏‎‎‏‏‏‎‏‏‏‎‎‎‎‎‎‎‏‎‎‎‏‏‏‏‎‎‏‎‏‎‎‎‎‎‏‎‎‎‏‏‏‏‎‎‏‎Create passkey in‎‏‎‎‏‎"</string>
+ <string name="save_password_to_title" msgid="3450480045270186421">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‏‏‏‏‏‏‎‎‎‏‎‏‎‎‏‎‎‎‏‎‏‎‏‎‎‎‏‏‏‎‏‏‎‏‎‏‏‏‎‎‏‏‏‎‎‏‏‏‎‎‏‏‎‏‏‎‏‎‏‎Save password to‎‏‎‎‏‎"</string>
+ <string name="save_sign_in_to_title" msgid="8328143607671760232">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‏‏‏‎‎‏‎‎‏‏‏‎‎‎‎‎‏‎‎‏‏‏‏‏‎‎‏‎‎‏‏‏‎‏‏‎‏‏‎‏‏‏‎‎‏‎‎‏‎‏‎‏‏‎‏‎‎‎‎Save sign-in to‎‏‎‎‏‎"</string>
+ <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‎‎‎‏‏‏‎‎‎‏‎‏‎‎‎‏‏‎‎‏‏‏‏‏‏‏‎‎‏‏‏‎‎‎‏‎‏‏‏‏‎‏‎‎‏‎‏‎‏‎‎‏‎‎‏‎‎‎‎Create a passkey in another device?‎‏‎‎‏‎"</string>
<string name="use_provider_for_all_title" msgid="4201020195058980757">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‏‎‎‏‎‎‏‏‎‏‎‎‎‎‎‏‎‏‏‎‎‏‏‏‎‎‎‎‎‎‎‏‎‎‏‎‏‏‏‎‏‏‏‎‎‏‎‏‏‏‏‎‎‏‎‏‎‏‎Use ‎‏‎‎‏‏‎<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ for all your sign-ins?‎‏‎‎‏‎"</string>
- <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
- <skip />
+ <string name="use_provider_for_all_description" msgid="6560593199974037820">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‏‏‎‎‎‎‏‎‏‏‏‏‏‎‏‎‎‎‏‏‎‎‎‎‎‏‏‎‎‎‎‎‎‎‎‏‎‎‏‎‏‏‏‎‏‏‏‎‎‏‎‎‏‏‏‏‎‎‎This password manager will store your passwords and passkeys to help you easily sign in.‎‏‎‎‏‎"</string>
<string name="set_as_default" msgid="4415328591568654603">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‎‏‎‏‎‎‎‏‏‎‎‏‏‎‎‏‎‏‏‏‏‏‎‏‎‏‏‏‏‎‏‎‏‏‎‎‎‏‎‎‏‏‎‏‎‏‎‎‎‏‎‎‎‎‏‎‏‏‎Set as default‎‏‎‎‏‎"</string>
<string name="use_once" msgid="9027366575315399714">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‎‏‎‏‎‎‎‏‏‏‏‎‏‎‎‏‏‎‎‎‎‏‎‎‏‎‎‎‏‏‏‎‏‎‏‏‏‎‏‎‎‎‏‎‏‏‎‏‎‎‎‎‏‎‎‎‏‎‎Use once‎‏‎‎‏‎"</string>
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‏‎‏‎‎‎‏‎‏‎‏‏‏‎‏‎‏‏‏‏‎‎‎‎‏‏‎‏‏‏‏‏‎‎‎‏‎‏‎‎‏‏‏‏‏‎‏‏‎‏‏‎‎‏‎‎‎‏‎‎‏‎‎‏‏‎<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>‎‏‎‎‏‏‏‎ passwords, ‎‏‎‎‏‏‎<xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>‎‏‎‎‏‏‏‎ passkeys‎‏‎‎‏‎"</string>
diff --git a/packages/CredentialManager/res/values-es-rUS/strings.xml b/packages/CredentialManager/res/values-es-rUS/strings.xml
index 96e769754101..5b8e4429ed94 100644
--- a/packages/CredentialManager/res/values-es-rUS/strings.xml
+++ b/packages/CredentialManager/res/values-es-rUS/strings.xml
@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for app_name (4539824758261855508) -->
- <skip />
+ <string name="app_name" msgid="4539824758261855508">"Credential Manager"</string>
<string name="string_cancel" msgid="6369133483981306063">"Cancelar"</string>
<string name="string_continue" msgid="1346732695941131882">"Continuar"</string>
<string name="string_create_in_another_place" msgid="1033635365843437603">"Crear en otra ubicación"</string>
@@ -31,6 +30,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"¿Quieres usar <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> para todos tus accesos?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
@@ -39,8 +40,7 @@
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> llaves de acceso, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> contraseñas"</string>
<string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> contraseñas"</string>
<string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> llaves de acceso"</string>
- <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
- <skip />
+ <string name="passkey_before_subtitle" msgid="2448119456208647444">"Llave de acceso"</string>
<string name="another_device" msgid="5147276802037801217">"Otro dispositivo"</string>
<string name="other_password_manager" msgid="565790221427004141">"Otros administradores de contraseñas"</string>
<string name="close_sheet" msgid="1393792015338908262">"Cerrar hoja"</string>
diff --git a/packages/CredentialManager/res/values-es/strings.xml b/packages/CredentialManager/res/values-es/strings.xml
index dce1a8ea7656..19fde728ac0e 100644
--- a/packages/CredentialManager/res/values-es/strings.xml
+++ b/packages/CredentialManager/res/values-es/strings.xml
@@ -31,6 +31,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"¿Usar <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> para todos tus inicios de sesión?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
diff --git a/packages/CredentialManager/res/values-et/strings.xml b/packages/CredentialManager/res/values-et/strings.xml
index 00396e007808..5b1b0705c053 100644
--- a/packages/CredentialManager/res/values-et/strings.xml
+++ b/packages/CredentialManager/res/values-et/strings.xml
@@ -31,6 +31,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Kas kasutada teenust <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> kõigi teie sisselogimisandmete puhul?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
diff --git a/packages/CredentialManager/res/values-eu/strings.xml b/packages/CredentialManager/res/values-eu/strings.xml
index 38f659204830..b2c1fe5252dd 100644
--- a/packages/CredentialManager/res/values-eu/strings.xml
+++ b/packages/CredentialManager/res/values-eu/strings.xml
@@ -31,6 +31,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> erabili nahi duzu kredentzial guztietarako?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
diff --git a/packages/CredentialManager/res/values-fa/strings.xml b/packages/CredentialManager/res/values-fa/strings.xml
index fdfd1e39eb48..98b487caf026 100644
--- a/packages/CredentialManager/res/values-fa/strings.xml
+++ b/packages/CredentialManager/res/values-fa/strings.xml
@@ -31,6 +31,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"از <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> برای همه ورود به سیستم‌ها استفاده شود؟"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
diff --git a/packages/CredentialManager/res/values-fi/strings.xml b/packages/CredentialManager/res/values-fi/strings.xml
index 26cfbe5fe890..9ad178a456bf 100644
--- a/packages/CredentialManager/res/values-fi/strings.xml
+++ b/packages/CredentialManager/res/values-fi/strings.xml
@@ -8,8 +8,7 @@
<string name="string_create_in_another_place" msgid="1033635365843437603">"Luo muualla"</string>
<string name="string_save_to_another_place" msgid="7590325934591079193">"Tallenna muualle"</string>
<string name="string_use_another_device" msgid="8754514926121520445">"Käytä toista laitetta"</string>
- <!-- no translation found for string_save_to_another_device (1959562542075194458) -->
- <skip />
+ <string name="string_save_to_another_device" msgid="1959562542075194458">"Tallenna toiselle laitteelle"</string>
<string name="passkey_creation_intro_title" msgid="402553911484409884">"Helppo tapa kirjautua turvallisesti sisään"</string>
<string name="passkey_creation_intro_body" msgid="7493320456005579290">"Käytä sormenjälkeä, kasvoja tai näytön lukitusta, niin voit kirjautua sisään yksilöllisellä avainkoodilla, jota ei voi unohtaa tai varastaa. Lue lisää"</string>
<string name="choose_provider_title" msgid="7245243990139698508">"Valitse paikka: <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
@@ -32,6 +31,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Otetaanko <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> käyttöön kaikissa sisäänkirjautumisissa?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
diff --git a/packages/CredentialManager/res/values-fr-rCA/strings.xml b/packages/CredentialManager/res/values-fr-rCA/strings.xml
index ef3d3252c698..a2e758173770 100644
--- a/packages/CredentialManager/res/values-fr-rCA/strings.xml
+++ b/packages/CredentialManager/res/values-fr-rCA/strings.xml
@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for app_name (4539824758261855508) -->
- <skip />
+ <string name="app_name" msgid="4539824758261855508">"Gestionnaire d\'identifiants"</string>
<string name="string_cancel" msgid="6369133483981306063">"Annuler"</string>
<string name="string_continue" msgid="1346732695941131882">"Continuer"</string>
<string name="string_create_in_another_place" msgid="1033635365843437603">"Créer à un autre emplacement"</string>
@@ -31,6 +30,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Utiliser <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> pour toutes vos connexions?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
@@ -39,8 +40,7 @@
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> mots de passe, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> clés d\'accès"</string>
<string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> mots de passe"</string>
<string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> clés d\'accès"</string>
- <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
- <skip />
+ <string name="passkey_before_subtitle" msgid="2448119456208647444">"Clé d\'accès"</string>
<string name="another_device" msgid="5147276802037801217">"Un autre appareil"</string>
<string name="other_password_manager" msgid="565790221427004141">"Autres gestionnaires de mots de passe"</string>
<string name="close_sheet" msgid="1393792015338908262">"Fermer la feuille"</string>
diff --git a/packages/CredentialManager/res/values-fr/strings.xml b/packages/CredentialManager/res/values-fr/strings.xml
index f660ddeb70df..2b280fb7a2a1 100644
--- a/packages/CredentialManager/res/values-fr/strings.xml
+++ b/packages/CredentialManager/res/values-fr/strings.xml
@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for app_name (4539824758261855508) -->
- <skip />
+ <string name="app_name" msgid="4539824758261855508">"Gestionnaire d\'identifiants"</string>
<string name="string_cancel" msgid="6369133483981306063">"Annuler"</string>
<string name="string_continue" msgid="1346732695941131882">"Continuer"</string>
<string name="string_create_in_another_place" msgid="1033635365843437603">"Créer ailleurs"</string>
@@ -31,6 +30,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Utiliser <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> pour toutes vos connexions ?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
@@ -39,8 +40,7 @@
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> mots de passe, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> clés d\'accès"</string>
<string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> mots de passe"</string>
<string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> clés d\'accès"</string>
- <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
- <skip />
+ <string name="passkey_before_subtitle" msgid="2448119456208647444">"Clé d\'accès"</string>
<string name="another_device" msgid="5147276802037801217">"Un autre appareil"</string>
<string name="other_password_manager" msgid="565790221427004141">"Autres gestionnaires de mots de passe"</string>
<string name="close_sheet" msgid="1393792015338908262">"Fermer la feuille"</string>
diff --git a/packages/CredentialManager/res/values-gl/strings.xml b/packages/CredentialManager/res/values-gl/strings.xml
index cacec21c801e..cc03ca4ac3a0 100644
--- a/packages/CredentialManager/res/values-gl/strings.xml
+++ b/packages/CredentialManager/res/values-gl/strings.xml
@@ -1,15 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for app_name (4539824758261855508) -->
- <skip />
+ <string name="app_name" msgid="4539824758261855508">"Xestor de credenciais"</string>
<string name="string_cancel" msgid="6369133483981306063">"Cancelar"</string>
<string name="string_continue" msgid="1346732695941131882">"Continuar"</string>
<string name="string_create_in_another_place" msgid="1033635365843437603">"Crear noutro lugar"</string>
<string name="string_save_to_another_place" msgid="7590325934591079193">"Gardar noutro lugar"</string>
<string name="string_use_another_device" msgid="8754514926121520445">"Usar outro dispositivo"</string>
- <!-- no translation found for string_save_to_another_device (1959562542075194458) -->
- <skip />
+ <string name="string_save_to_another_device" msgid="1959562542075194458">"Gardar noutro dispositivo"</string>
<string name="passkey_creation_intro_title" msgid="402553911484409884">"Un xeito fácil de iniciar sesión de forma segura"</string>
<string name="passkey_creation_intro_body" msgid="7493320456005579290">"Usa a impresión dixital, a cara ou o bloqueo de pantalla para iniciar sesión cunha clave de acceso única que non podes esquecer nin cha poden roubar. Máis información"</string>
<string name="choose_provider_title" msgid="7245243990139698508">"Escolle onde <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
@@ -32,6 +30,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Queres usar <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> cada vez que inicies sesión?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
@@ -40,8 +40,7 @@
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> contrasinais, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> claves de acceso"</string>
<string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> contrasinais"</string>
<string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> claves de acceso"</string>
- <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
- <skip />
+ <string name="passkey_before_subtitle" msgid="2448119456208647444">"Clave de acceso"</string>
<string name="another_device" msgid="5147276802037801217">"Outro dispositivo"</string>
<string name="other_password_manager" msgid="565790221427004141">"Outros xestores de contrasinais"</string>
<string name="close_sheet" msgid="1393792015338908262">"Pechar folla"</string>
diff --git a/packages/CredentialManager/res/values-gu/strings.xml b/packages/CredentialManager/res/values-gu/strings.xml
index 7ac70aa91dcc..f796d207823e 100644
--- a/packages/CredentialManager/res/values-gu/strings.xml
+++ b/packages/CredentialManager/res/values-gu/strings.xml
@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for app_name (4539824758261855508) -->
- <skip />
+ <string name="app_name" msgid="4539824758261855508">"લૉગ ઇન વિગતોના મેનેજર"</string>
<string name="string_cancel" msgid="6369133483981306063">"રદ કરો"</string>
<string name="string_continue" msgid="1346732695941131882">"ચાલુ રાખો"</string>
<string name="string_create_in_another_place" msgid="1033635365843437603">"કોઈ અન્ય સ્થાન પર બનાવો"</string>
@@ -31,6 +30,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"શું તમારા બધા સાઇન-ઇન માટે <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>નો ઉપયોગ કરીએ?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
@@ -39,8 +40,7 @@
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> પાસવર્ડ, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> પાસકી"</string>
<string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> પાસવર્ડ"</string>
<string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> પાસકી"</string>
- <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
- <skip />
+ <string name="passkey_before_subtitle" msgid="2448119456208647444">"પાસકી"</string>
<string name="another_device" msgid="5147276802037801217">"કોઈ અન્ય ડિવાઇસ"</string>
<string name="other_password_manager" msgid="565790221427004141">"અન્ય પાસવર્ડ મેનેજર"</string>
<string name="close_sheet" msgid="1393792015338908262">"શીટ બંધ કરો"</string>
diff --git a/packages/CredentialManager/res/values-hi/strings.xml b/packages/CredentialManager/res/values-hi/strings.xml
index 8d28e0f90abc..fbf1c0214aac 100644
--- a/packages/CredentialManager/res/values-hi/strings.xml
+++ b/packages/CredentialManager/res/values-hi/strings.xml
@@ -31,6 +31,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"क्या आपको साइन इन से जुड़ी सारी जानकारी सेव करने के लिए, <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> का इस्तेमाल करना है?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
diff --git a/packages/CredentialManager/res/values-hr/strings.xml b/packages/CredentialManager/res/values-hr/strings.xml
index 06db58393092..6c1952f38da3 100644
--- a/packages/CredentialManager/res/values-hr/strings.xml
+++ b/packages/CredentialManager/res/values-hr/strings.xml
@@ -31,6 +31,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Želite li upotrebljavati uslugu <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> za sve prijave?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
diff --git a/packages/CredentialManager/res/values-hu/strings.xml b/packages/CredentialManager/res/values-hu/strings.xml
index 738de3a5482b..0efa3e8ec537 100644
--- a/packages/CredentialManager/res/values-hu/strings.xml
+++ b/packages/CredentialManager/res/values-hu/strings.xml
@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for app_name (4539824758261855508) -->
- <skip />
+ <string name="app_name" msgid="4539824758261855508">"Tanúsítványkezelő"</string>
<string name="string_cancel" msgid="6369133483981306063">"Mégse"</string>
<string name="string_continue" msgid="1346732695941131882">"Folytatás"</string>
<string name="string_create_in_another_place" msgid="1033635365843437603">"Létrehozás másik helyen"</string>
@@ -31,6 +30,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Szeretné a következőt használni az összes bejelentkezési adatához: <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
@@ -39,8 +40,7 @@
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> jelszó, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> azonosítókulcs"</string>
<string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> jelszó"</string>
<string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> azonosítókulcs"</string>
- <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
- <skip />
+ <string name="passkey_before_subtitle" msgid="2448119456208647444">"Azonosítókulcs"</string>
<string name="another_device" msgid="5147276802037801217">"Másik eszköz"</string>
<string name="other_password_manager" msgid="565790221427004141">"Egyéb jelszókezelők"</string>
<string name="close_sheet" msgid="1393792015338908262">"Munkalap bezárása"</string>
diff --git a/packages/CredentialManager/res/values-hy/strings.xml b/packages/CredentialManager/res/values-hy/strings.xml
index 7320e6411248..de47e9f5858c 100644
--- a/packages/CredentialManager/res/values-hy/strings.xml
+++ b/packages/CredentialManager/res/values-hy/strings.xml
@@ -31,6 +31,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Միշտ մուտք գործե՞լ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> հավելվածի միջոցով"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
diff --git a/packages/CredentialManager/res/values-in/strings.xml b/packages/CredentialManager/res/values-in/strings.xml
index 827a4ff2b33d..d980d44c7f32 100644
--- a/packages/CredentialManager/res/values-in/strings.xml
+++ b/packages/CredentialManager/res/values-in/strings.xml
@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for app_name (4539824758261855508) -->
- <skip />
+ <string name="app_name" msgid="4539824758261855508">"Pengelola Kredensial"</string>
<string name="string_cancel" msgid="6369133483981306063">"Batal"</string>
<string name="string_continue" msgid="1346732695941131882">"Lanjutkan"</string>
<string name="string_create_in_another_place" msgid="1033635365843437603">"Buat di tempat lain"</string>
@@ -31,6 +30,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Gunakan <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> untuk semua info login Anda?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
@@ -39,8 +40,7 @@
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> sandi, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> kunci sandi"</string>
<string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> sandi"</string>
<string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> kunci sandi"</string>
- <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
- <skip />
+ <string name="passkey_before_subtitle" msgid="2448119456208647444">"Kunci sandi"</string>
<string name="another_device" msgid="5147276802037801217">"Perangkat lain"</string>
<string name="other_password_manager" msgid="565790221427004141">"Pengelola sandi lainnya"</string>
<string name="close_sheet" msgid="1393792015338908262">"Tutup sheet"</string>
diff --git a/packages/CredentialManager/res/values-is/strings.xml b/packages/CredentialManager/res/values-is/strings.xml
index c52e5f7815f5..3fd6af2456b9 100644
--- a/packages/CredentialManager/res/values-is/strings.xml
+++ b/packages/CredentialManager/res/values-is/strings.xml
@@ -31,6 +31,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Nota <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> fyrir allar innskráningar?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
diff --git a/packages/CredentialManager/res/values-it/strings.xml b/packages/CredentialManager/res/values-it/strings.xml
index a06135e0ac0f..3a7b0fb1ec3a 100644
--- a/packages/CredentialManager/res/values-it/strings.xml
+++ b/packages/CredentialManager/res/values-it/strings.xml
@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for app_name (4539824758261855508) -->
- <skip />
+ <string name="app_name" msgid="4539824758261855508">"Gestore delle credenziali"</string>
<string name="string_cancel" msgid="6369133483981306063">"Annulla"</string>
<string name="string_continue" msgid="1346732695941131882">"Continua"</string>
<string name="string_create_in_another_place" msgid="1033635365843437603">"Crea in un altro luogo"</string>
@@ -31,6 +30,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Vuoi usare <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> per tutti gli accessi?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
@@ -39,8 +40,7 @@
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> password, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> passkey"</string>
<string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> password"</string>
<string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> passkey"</string>
- <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
- <skip />
+ <string name="passkey_before_subtitle" msgid="2448119456208647444">"Passkey"</string>
<string name="another_device" msgid="5147276802037801217">"Un altro dispositivo"</string>
<string name="other_password_manager" msgid="565790221427004141">"Altri gestori delle password"</string>
<string name="close_sheet" msgid="1393792015338908262">"Chiudi il foglio"</string>
diff --git a/packages/CredentialManager/res/values-iw/strings.xml b/packages/CredentialManager/res/values-iw/strings.xml
index e9c6adb744c7..397ad60e79f7 100644
--- a/packages/CredentialManager/res/values-iw/strings.xml
+++ b/packages/CredentialManager/res/values-iw/strings.xml
@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for app_name (4539824758261855508) -->
- <skip />
+ <string name="app_name" msgid="4539824758261855508">"מנהל פרטי הכניסה"</string>
<string name="string_cancel" msgid="6369133483981306063">"ביטול"</string>
<string name="string_continue" msgid="1346732695941131882">"המשך"</string>
<string name="string_create_in_another_place" msgid="1033635365843437603">"יצירה במקום אחר"</string>
@@ -31,6 +30,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"להשתמש ב-<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> בכל הכניסות?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
@@ -39,8 +40,7 @@
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> סיסמאות, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> מפתחות גישה"</string>
<string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> סיסמאות"</string>
<string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> מפתחות גישה"</string>
- <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
- <skip />
+ <string name="passkey_before_subtitle" msgid="2448119456208647444">"מפתח גישה"</string>
<string name="another_device" msgid="5147276802037801217">"מכשיר אחר"</string>
<string name="other_password_manager" msgid="565790221427004141">"מנהלי סיסמאות אחרים"</string>
<string name="close_sheet" msgid="1393792015338908262">"סגירת הגיליון"</string>
diff --git a/packages/CredentialManager/res/values-ja/strings.xml b/packages/CredentialManager/res/values-ja/strings.xml
index 8e448eb5d420..0340b6698645 100644
--- a/packages/CredentialManager/res/values-ja/strings.xml
+++ b/packages/CredentialManager/res/values-ja/strings.xml
@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for app_name (4539824758261855508) -->
- <skip />
+ <string name="app_name" msgid="4539824758261855508">"認証情報マネージャー"</string>
<string name="string_cancel" msgid="6369133483981306063">"キャンセル"</string>
<string name="string_continue" msgid="1346732695941131882">"続行"</string>
<string name="string_create_in_another_place" msgid="1033635365843437603">"別の場所で作成"</string>
@@ -31,6 +30,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"ログインのたびに <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> を使用しますか?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
@@ -39,8 +40,7 @@
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> 件のパスワード、<xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> 件のパスキー"</string>
<string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> 件のパスワード"</string>
<string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> 件のパスキー"</string>
- <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
- <skip />
+ <string name="passkey_before_subtitle" msgid="2448119456208647444">"パスキー"</string>
<string name="another_device" msgid="5147276802037801217">"別のデバイス"</string>
<string name="other_password_manager" msgid="565790221427004141">"他のパスワード マネージャー"</string>
<string name="close_sheet" msgid="1393792015338908262">"シートを閉じます"</string>
diff --git a/packages/CredentialManager/res/values-ka/strings.xml b/packages/CredentialManager/res/values-ka/strings.xml
index 853ea19fa0e9..3da7ea394c60 100644
--- a/packages/CredentialManager/res/values-ka/strings.xml
+++ b/packages/CredentialManager/res/values-ka/strings.xml
@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for app_name (4539824758261855508) -->
- <skip />
+ <string name="app_name" msgid="4539824758261855508">"ავტორიზაციის მონაცემების მმართველი"</string>
<string name="string_cancel" msgid="6369133483981306063">"გაუქმება"</string>
<string name="string_continue" msgid="1346732695941131882">"გაგრძელება"</string>
<string name="string_create_in_another_place" msgid="1033635365843437603">"სხვა სივრცეში შექმნა"</string>
@@ -31,6 +30,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"გსურთ, გამოიყენოთ<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> სისტემაში ყველა შესვლისთვის?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
@@ -39,8 +40,7 @@
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> პაროლი, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> წვდომის გასაღები"</string>
<string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> პაროლი"</string>
<string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> წვდომის გასაღები"</string>
- <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
- <skip />
+ <string name="passkey_before_subtitle" msgid="2448119456208647444">"წვდომის გასაღები"</string>
<string name="another_device" msgid="5147276802037801217">"სხვა მოწყობილობა"</string>
<string name="other_password_manager" msgid="565790221427004141">"პაროლების სხვა მმართველები"</string>
<string name="close_sheet" msgid="1393792015338908262">"ფურცლის დახურვა"</string>
diff --git a/packages/CredentialManager/res/values-kk/strings.xml b/packages/CredentialManager/res/values-kk/strings.xml
index 2271533e650d..9491f8e83d6b 100644
--- a/packages/CredentialManager/res/values-kk/strings.xml
+++ b/packages/CredentialManager/res/values-kk/strings.xml
@@ -8,8 +8,7 @@
<string name="string_create_in_another_place" msgid="1033635365843437603">"Басқа орында жасау"</string>
<string name="string_save_to_another_place" msgid="7590325934591079193">"Басқа орынға сақтау"</string>
<string name="string_use_another_device" msgid="8754514926121520445">"Басқа құрылғыны пайдалану"</string>
- <!-- no translation found for string_save_to_another_device (1959562542075194458) -->
- <skip />
+ <string name="string_save_to_another_device" msgid="1959562542075194458">"Басқа құрылғыға сақтау"</string>
<string name="passkey_creation_intro_title" msgid="402553911484409884">"Қауіпсіз кірудің оңай жолы"</string>
<string name="passkey_creation_intro_body" msgid="7493320456005579290">"Ұмытылмайтын немесе ұрланбайтын бірегей кіру кілтінің көмегімен кіру үшін саусақ ізін, бетті анықтау функциясын немесе экран құлпын пайдаланыңыз. Толық ақпарат"</string>
<string name="choose_provider_title" msgid="7245243990139698508">"<xliff:g id="CREATETYPES">%1$s</xliff:g> таңдау"</string>
@@ -32,6 +31,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Барлық кіру әрекеті үшін <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> пайдаланылсын ба?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
diff --git a/packages/CredentialManager/res/values-km/strings.xml b/packages/CredentialManager/res/values-km/strings.xml
index d51781009950..80167fc36e35 100644
--- a/packages/CredentialManager/res/values-km/strings.xml
+++ b/packages/CredentialManager/res/values-km/strings.xml
@@ -31,6 +31,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"ប្រើ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> សម្រាប់ការចូលគណនីទាំងអស់របស់អ្នកឬ?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
diff --git a/packages/CredentialManager/res/values-kn/strings.xml b/packages/CredentialManager/res/values-kn/strings.xml
index 763f8eef9203..96304ac96d3b 100644
--- a/packages/CredentialManager/res/values-kn/strings.xml
+++ b/packages/CredentialManager/res/values-kn/strings.xml
@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for app_name (4539824758261855508) -->
- <skip />
+ <string name="app_name" msgid="4539824758261855508">"ರುಜುವಾತು ನಿರ್ವಾಹಕ"</string>
<string name="string_cancel" msgid="6369133483981306063">"ರದ್ದುಗೊಳಿಸಿ"</string>
<string name="string_continue" msgid="1346732695941131882">"ಮುಂದುವರಿಸಿ"</string>
<string name="string_create_in_another_place" msgid="1033635365843437603">"ಮತ್ತೊಂದು ಸ್ಥಳದಲ್ಲಿ ರಚಿಸಿ"</string>
@@ -31,6 +30,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"ನಿಮ್ಮ ಎಲ್ಲಾ ಸೈನ್-ಇನ್‌ಗಳಿಗಾಗಿ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ಅನ್ನು ಬಳಸುವುದೇ?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
@@ -39,8 +40,7 @@
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> ಪಾಸ್‌ವರ್ಡ್‌ಗಳು, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> ಪಾಸ್‌ಕೀಗಳು"</string>
<string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> ಪಾಸ್‌ವರ್ಡ್‌ಗಳು"</string>
<string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> ಪಾಸ್‌ಕೀಗಳು"</string>
- <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
- <skip />
+ <string name="passkey_before_subtitle" msgid="2448119456208647444">"ಪಾಸ್‌ಕೀ"</string>
<string name="another_device" msgid="5147276802037801217">"ಮತ್ತೊಂದು ಸಾಧನ"</string>
<string name="other_password_manager" msgid="565790221427004141">"ಇತರ ಪಾಸ್‌ವರ್ಡ್ ನಿರ್ವಾಹಕರು"</string>
<string name="close_sheet" msgid="1393792015338908262">"ಶೀಟ್ ಮುಚ್ಚಿರಿ"</string>
diff --git a/packages/CredentialManager/res/values-ko/strings.xml b/packages/CredentialManager/res/values-ko/strings.xml
index 246790d1c774..58518c16ace4 100644
--- a/packages/CredentialManager/res/values-ko/strings.xml
+++ b/packages/CredentialManager/res/values-ko/strings.xml
@@ -31,6 +31,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"모든 로그인에 <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>을(를) 사용하시겠습니까?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
diff --git a/packages/CredentialManager/res/values-ky/strings.xml b/packages/CredentialManager/res/values-ky/strings.xml
index 3dc7deaabd42..298657eb8eaa 100644
--- a/packages/CredentialManager/res/values-ky/strings.xml
+++ b/packages/CredentialManager/res/values-ky/strings.xml
@@ -8,8 +8,7 @@
<string name="string_create_in_another_place" msgid="1033635365843437603">"Башка жерде түзүү"</string>
<string name="string_save_to_another_place" msgid="7590325934591079193">"Башка жерге сактоо"</string>
<string name="string_use_another_device" msgid="8754514926121520445">"Башка түзмөк колдонуу"</string>
- <!-- no translation found for string_save_to_another_device (1959562542075194458) -->
- <skip />
+ <string name="string_save_to_another_device" msgid="1959562542075194458">"Башка түзмөккө сактоо"</string>
<string name="passkey_creation_intro_title" msgid="402553911484409884">"Коопсуз кирүүнүн жөнөкөй жолу"</string>
<string name="passkey_creation_intro_body" msgid="7493320456005579290">"Унутуп калууга же уурдатууга мүмкүн эмес болгон уникалдуу ачкыч менен манжа изин, жүзүнөн таанып ачуу же экранды кулпулоо функцияларын колдонуп өзүңүздү ырастай аласыз. Кененирээк"</string>
<string name="choose_provider_title" msgid="7245243990139698508">"<xliff:g id="CREATETYPES">%1$s</xliff:g> үчүн жер тандаңыз"</string>
@@ -32,6 +31,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> бардык аккаунттарга кирүү үчүн колдонулсунбу?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
diff --git a/packages/CredentialManager/res/values-lo/strings.xml b/packages/CredentialManager/res/values-lo/strings.xml
index 8efc8a8d31f1..215262bc4943 100644
--- a/packages/CredentialManager/res/values-lo/strings.xml
+++ b/packages/CredentialManager/res/values-lo/strings.xml
@@ -31,6 +31,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"ໃຊ້ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ສຳລັບການເຂົ້າສູ່ລະບົບທັງໝົດຂອງທ່ານບໍ?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
diff --git a/packages/CredentialManager/res/values-lt/strings.xml b/packages/CredentialManager/res/values-lt/strings.xml
index 02d37837ad43..6125fe3f2b7b 100644
--- a/packages/CredentialManager/res/values-lt/strings.xml
+++ b/packages/CredentialManager/res/values-lt/strings.xml
@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for app_name (4539824758261855508) -->
- <skip />
+ <string name="app_name" msgid="4539824758261855508">"Prisijungimo duomenų tvarkytuvė"</string>
<string name="string_cancel" msgid="6369133483981306063">"Atšaukti"</string>
<string name="string_continue" msgid="1346732695941131882">"Tęsti"</string>
<string name="string_create_in_another_place" msgid="1033635365843437603">"Sukurti kitoje vietoje"</string>
@@ -31,6 +30,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Naudoti <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> visada prisijungiant?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
@@ -39,8 +40,7 @@
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"slaptažodžių: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>, „passkey“: <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>"</string>
<string name="more_options_usage_passwords" msgid="1632047277723187813">"slaptažodžių: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>"</string>
<string name="more_options_usage_passkeys" msgid="5390320437243042237">"„passkey“: <xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g>"</string>
- <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
- <skip />
+ <string name="passkey_before_subtitle" msgid="2448119456208647444">"Slaptažodis"</string>
<string name="another_device" msgid="5147276802037801217">"Kitas įrenginys"</string>
<string name="other_password_manager" msgid="565790221427004141">"Kitos slaptažodžių tvarkyklės"</string>
<string name="close_sheet" msgid="1393792015338908262">"Uždaryti lapą"</string>
diff --git a/packages/CredentialManager/res/values-lv/strings.xml b/packages/CredentialManager/res/values-lv/strings.xml
index cbea91aa917c..43c036fde4bb 100644
--- a/packages/CredentialManager/res/values-lv/strings.xml
+++ b/packages/CredentialManager/res/values-lv/strings.xml
@@ -1,15 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for app_name (4539824758261855508) -->
- <skip />
+ <string name="app_name" msgid="4539824758261855508">"Akreditācijas datu pārvaldnieks"</string>
<string name="string_cancel" msgid="6369133483981306063">"Atcelt"</string>
<string name="string_continue" msgid="1346732695941131882">"Turpināt"</string>
<string name="string_create_in_another_place" msgid="1033635365843437603">"Izveidot citur"</string>
<string name="string_save_to_another_place" msgid="7590325934591079193">"Saglabāt citur"</string>
<string name="string_use_another_device" msgid="8754514926121520445">"Izmantot citu ierīci"</string>
- <!-- no translation found for string_save_to_another_device (1959562542075194458) -->
- <skip />
+ <string name="string_save_to_another_device" msgid="1959562542075194458">"Saglabāt citā ierīcē"</string>
<string name="passkey_creation_intro_title" msgid="402553911484409884">"Vienkāršs veids, kā droši pierakstīties"</string>
<string name="passkey_creation_intro_body" msgid="7493320456005579290">"Izmantojiet pirksta nospiedumu, autorizāciju pēc sejas vai ekrāna bloķēšanu, lai pierakstītos ar unikālu piekļuves atslēgu, ko nevar aizmirst vai nozagt. Uzziniet vairāk."</string>
<string name="choose_provider_title" msgid="7245243990139698508">"Izvēlieties, kur: <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
@@ -32,6 +30,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Vai vienmēr izmantot <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>, lai pierakstītos?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
@@ -40,8 +40,7 @@
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> paroles, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> piekļuves atslēgas"</string>
<string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> paroles"</string>
<string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> piekļuves atslēgas"</string>
- <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
- <skip />
+ <string name="passkey_before_subtitle" msgid="2448119456208647444">"Piekļuves atslēga"</string>
<string name="another_device" msgid="5147276802037801217">"Cita ierīce"</string>
<string name="other_password_manager" msgid="565790221427004141">"Citi paroļu pārvaldnieki"</string>
<string name="close_sheet" msgid="1393792015338908262">"Aizvērt lapu"</string>
diff --git a/packages/CredentialManager/res/values-mk/strings.xml b/packages/CredentialManager/res/values-mk/strings.xml
index e98bfc48a864..059f04207958 100644
--- a/packages/CredentialManager/res/values-mk/strings.xml
+++ b/packages/CredentialManager/res/values-mk/strings.xml
@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for app_name (4539824758261855508) -->
- <skip />
+ <string name="app_name" msgid="4539824758261855508">"Управник на акредитиви"</string>
<string name="string_cancel" msgid="6369133483981306063">"Откажи"</string>
<string name="string_continue" msgid="1346732695941131882">"Продолжи"</string>
<string name="string_create_in_another_place" msgid="1033635365843437603">"Создајте на друго место"</string>
@@ -31,6 +30,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Да се користи <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> за сите ваши најавувања?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
@@ -39,8 +40,7 @@
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> лозинки, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> криптографски клучеви"</string>
<string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> лозинки"</string>
<string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> криптографски клучеви"</string>
- <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
- <skip />
+ <string name="passkey_before_subtitle" msgid="2448119456208647444">"Криптографски клуч"</string>
<string name="another_device" msgid="5147276802037801217">"Друг уред"</string>
<string name="other_password_manager" msgid="565790221427004141">"Други управници со лозинки"</string>
<string name="close_sheet" msgid="1393792015338908262">"Затворете го листот"</string>
diff --git a/packages/CredentialManager/res/values-ml/strings.xml b/packages/CredentialManager/res/values-ml/strings.xml
index 34029cec907b..e4f6d6972698 100644
--- a/packages/CredentialManager/res/values-ml/strings.xml
+++ b/packages/CredentialManager/res/values-ml/strings.xml
@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for app_name (4539824758261855508) -->
- <skip />
+ <string name="app_name" msgid="4539824758261855508">"ക്രെഡൻഷ്യൽ മാനേജർ"</string>
<string name="string_cancel" msgid="6369133483981306063">"റദ്ദാക്കുക"</string>
<string name="string_continue" msgid="1346732695941131882">"തുടരുക"</string>
<string name="string_create_in_another_place" msgid="1033635365843437603">"മറ്റൊരു സ്ഥലത്ത് സൃഷ്‌ടിക്കുക"</string>
@@ -31,6 +30,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"നിങ്ങളുടെ എല്ലാ സൈൻ ഇന്നുകൾക്കും <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ഉപയോഗിക്കണോ?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
@@ -39,8 +40,7 @@
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> പാസ്‌വേഡുകൾ, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> പാസ്‌കീകൾ"</string>
<string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> പാസ്‌വേഡുകൾ"</string>
<string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> പാസ്‌കീകൾ"</string>
- <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
- <skip />
+ <string name="passkey_before_subtitle" msgid="2448119456208647444">"പാസ്‌കീ"</string>
<string name="another_device" msgid="5147276802037801217">"മറ്റൊരു ഉപകരണം"</string>
<string name="other_password_manager" msgid="565790221427004141">"മറ്റ് പാസ്‌വേഡ് മാനേജർമാർ"</string>
<string name="close_sheet" msgid="1393792015338908262">"ഷീറ്റ് അടയ്ക്കുക"</string>
diff --git a/packages/CredentialManager/res/values-mn/strings.xml b/packages/CredentialManager/res/values-mn/strings.xml
index f0909316755a..3f8d4caafafe 100644
--- a/packages/CredentialManager/res/values-mn/strings.xml
+++ b/packages/CredentialManager/res/values-mn/strings.xml
@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for app_name (4539824758261855508) -->
- <skip />
+ <string name="app_name" msgid="4539824758261855508">"Мандат үнэмлэхийн менежер"</string>
<string name="string_cancel" msgid="6369133483981306063">"Цуцлах"</string>
<string name="string_continue" msgid="1346732695941131882">"Үргэлжлүүлэх"</string>
<string name="string_create_in_another_place" msgid="1033635365843437603">"Өөр газар үүсгэх"</string>
@@ -31,6 +30,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>-г бүх нэвтрэлтдээ ашиглах уу?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
@@ -39,8 +40,7 @@
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> нууц үг, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> passkey"</string>
<string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> нууц үг"</string>
<string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> passkey"</string>
- <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
- <skip />
+ <string name="passkey_before_subtitle" msgid="2448119456208647444">"Passkey"</string>
<string name="another_device" msgid="5147276802037801217">"Өөр төхөөрөмж"</string>
<string name="other_password_manager" msgid="565790221427004141">"Нууц үгний бусад менежер"</string>
<string name="close_sheet" msgid="1393792015338908262">"Хүснэгтийг хаах"</string>
diff --git a/packages/CredentialManager/res/values-mr/strings.xml b/packages/CredentialManager/res/values-mr/strings.xml
index c4d12f54f0aa..aa6f2537842a 100644
--- a/packages/CredentialManager/res/values-mr/strings.xml
+++ b/packages/CredentialManager/res/values-mr/strings.xml
@@ -31,6 +31,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"तुमच्या सर्व साइन-इन साठी <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>वापरायचे का?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
diff --git a/packages/CredentialManager/res/values-ms/strings.xml b/packages/CredentialManager/res/values-ms/strings.xml
index fb130feb39b8..d5f8c0e59bca 100644
--- a/packages/CredentialManager/res/values-ms/strings.xml
+++ b/packages/CredentialManager/res/values-ms/strings.xml
@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for app_name (4539824758261855508) -->
- <skip />
+ <string name="app_name" msgid="4539824758261855508">"Pengurus Bukti Kelayakan"</string>
<string name="string_cancel" msgid="6369133483981306063">"Batal"</string>
<string name="string_continue" msgid="1346732695941131882">"Teruskan"</string>
<string name="string_create_in_another_place" msgid="1033635365843437603">"Buat di tempat lain"</string>
@@ -31,6 +30,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Gunakan <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> untuk semua log masuk anda?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
@@ -39,8 +40,7 @@
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"Kata laluan <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>, kunci laluan <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>"</string>
<string name="more_options_usage_passwords" msgid="1632047277723187813">"Kata laluan <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>"</string>
<string name="more_options_usage_passkeys" msgid="5390320437243042237">"Kunci laluan <xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g>"</string>
- <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
- <skip />
+ <string name="passkey_before_subtitle" msgid="2448119456208647444">"Kunci laluan"</string>
<string name="another_device" msgid="5147276802037801217">"Peranti lain"</string>
<string name="other_password_manager" msgid="565790221427004141">"Password Manager lain"</string>
<string name="close_sheet" msgid="1393792015338908262">"Tutup helaian"</string>
diff --git a/packages/CredentialManager/res/values-my/strings.xml b/packages/CredentialManager/res/values-my/strings.xml
index b14960ab0858..eda2f741eda7 100644
--- a/packages/CredentialManager/res/values-my/strings.xml
+++ b/packages/CredentialManager/res/values-my/strings.xml
@@ -31,6 +31,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"သင်၏လက်မှတ်ထိုးဝင်မှု အားလုံးအတွက် <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> သုံးမလား။"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
diff --git a/packages/CredentialManager/res/values-nb/strings.xml b/packages/CredentialManager/res/values-nb/strings.xml
index d53bc7e45cc6..82854b82e555 100644
--- a/packages/CredentialManager/res/values-nb/strings.xml
+++ b/packages/CredentialManager/res/values-nb/strings.xml
@@ -8,8 +8,7 @@
<string name="string_create_in_another_place" msgid="1033635365843437603">"Opprett på et annet sted"</string>
<string name="string_save_to_another_place" msgid="7590325934591079193">"Lagre på et annet sted"</string>
<string name="string_use_another_device" msgid="8754514926121520445">"Bruk en annen enhet"</string>
- <!-- no translation found for string_save_to_another_device (1959562542075194458) -->
- <skip />
+ <string name="string_save_to_another_device" msgid="1959562542075194458">"Lagre på en annen enhet"</string>
<string name="passkey_creation_intro_title" msgid="402553911484409884">"En enkel og trygg påloggingsmåte"</string>
<string name="passkey_creation_intro_body" msgid="7493320456005579290">"Bruk fingeravtrykk, ansiktet eller en skjermlås til å logge på med en unik tilgangsnøkkel du verken kan glemme eller miste. Finn ut mer"</string>
<string name="choose_provider_title" msgid="7245243990139698508">"Velg hvor <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
@@ -32,6 +31,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Vil du bruke <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> for alle pålogginger?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
diff --git a/packages/CredentialManager/res/values-ne/strings.xml b/packages/CredentialManager/res/values-ne/strings.xml
index 77b095944f39..23f4f430a799 100644
--- a/packages/CredentialManager/res/values-ne/strings.xml
+++ b/packages/CredentialManager/res/values-ne/strings.xml
@@ -31,6 +31,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"तपाईंले साइन इन गर्ने सबै डिभाइसहरूमा <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> प्रयोग गर्ने हो?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
diff --git a/packages/CredentialManager/res/values-nl/strings.xml b/packages/CredentialManager/res/values-nl/strings.xml
index a80c2883f2fb..c91a318e2073 100644
--- a/packages/CredentialManager/res/values-nl/strings.xml
+++ b/packages/CredentialManager/res/values-nl/strings.xml
@@ -1,15 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for app_name (4539824758261855508) -->
- <skip />
+ <string name="app_name" msgid="4539824758261855508">"Credential Manager"</string>
<string name="string_cancel" msgid="6369133483981306063">"Annuleren"</string>
<string name="string_continue" msgid="1346732695941131882">"Doorgaan"</string>
<string name="string_create_in_another_place" msgid="1033635365843437603">"Op een andere locatie maken"</string>
<string name="string_save_to_another_place" msgid="7590325934591079193">"Op een andere locatie opslaan"</string>
<string name="string_use_another_device" msgid="8754514926121520445">"Een ander apparaat gebruiken"</string>
- <!-- no translation found for string_save_to_another_device (1959562542075194458) -->
- <skip />
+ <string name="string_save_to_another_device" msgid="1959562542075194458">"Opslaan op een ander apparaat"</string>
<string name="passkey_creation_intro_title" msgid="402553911484409884">"Een makkelijke manier om beveiligd in te loggen"</string>
<string name="passkey_creation_intro_body" msgid="7493320456005579290">"Gebruik je vingerafdruk, gezichtsvergrendeling of schermvergrendeling om in te loggen met een unieke toegangssleutel die je niet kunt vergeten en die anderen niet kunnen stelen. Meer informatie"</string>
<string name="choose_provider_title" msgid="7245243990139698508">"Een locatie kiezen voor <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
@@ -32,6 +30,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> elke keer gebruiken als je inlogt?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
@@ -40,8 +40,7 @@
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> wachtwoorden, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> toegangssleutels"</string>
<string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> wachtwoorden"</string>
<string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> toegangssleutels"</string>
- <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
- <skip />
+ <string name="passkey_before_subtitle" msgid="2448119456208647444">"Toegangssleutel"</string>
<string name="another_device" msgid="5147276802037801217">"Een ander apparaat"</string>
<string name="other_password_manager" msgid="565790221427004141">"Andere wachtwoordmanagers"</string>
<string name="close_sheet" msgid="1393792015338908262">"Blad sluiten"</string>
diff --git a/packages/CredentialManager/res/values-or/strings.xml b/packages/CredentialManager/res/values-or/strings.xml
index 5b401db5d90b..838ddfe64a50 100644
--- a/packages/CredentialManager/res/values-or/strings.xml
+++ b/packages/CredentialManager/res/values-or/strings.xml
@@ -31,6 +31,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"ଆପଣଙ୍କ ସମସ୍ତ ସାଇନ-ଇନ ପାଇଁ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ବ୍ୟବହାର କରିବେ?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
diff --git a/packages/CredentialManager/res/values-pa/strings.xml b/packages/CredentialManager/res/values-pa/strings.xml
index f8f5ba159099..74b2ab123ec0 100644
--- a/packages/CredentialManager/res/values-pa/strings.xml
+++ b/packages/CredentialManager/res/values-pa/strings.xml
@@ -8,8 +8,7 @@
<string name="string_create_in_another_place" msgid="1033635365843437603">"ਕਿਸੇ ਹੋਰ ਥਾਂ \'ਤੇ ਬਣਾਓ"</string>
<string name="string_save_to_another_place" msgid="7590325934591079193">"ਕਿਸੇ ਹੋਰ ਥਾਂ \'ਤੇ ਰੱਖਿਅਤ ਕਰੋ"</string>
<string name="string_use_another_device" msgid="8754514926121520445">"ਕੋਈ ਹੋਰ ਡੀਵਾਈਸ ਦੀ ਵਰਤੋਂ ਕਰੋ"</string>
- <!-- no translation found for string_save_to_another_device (1959562542075194458) -->
- <skip />
+ <string name="string_save_to_another_device" msgid="1959562542075194458">"ਕਿਸੇ ਹੋਰ ਡੀਵਾਈਸ \'ਤੇ ਰੱਖਿਅਤ ਕਰੋ"</string>
<string name="passkey_creation_intro_title" msgid="402553911484409884">"ਸੁਰੱਖਿਅਤ ਢੰਗ ਨਾਲ ਸਾਈਨ-ਇਨ ਕਰਨ ਦਾ ਆਸਾਨ ਤਰੀਕਾ"</string>
<string name="passkey_creation_intro_body" msgid="7493320456005579290">"ਵਿਲੱਖਣ ਪਾਸਕੀ ਨਾਲ ਸਾਈਨ-ਇਨ ਕਰਨ ਵਾਸਤੇ ਆਪਣੇ ਫਿੰਗਰਪ੍ਰਿੰਟ, ਚਿਹਰੇ ਜਾਂ ਸਕ੍ਰੀਨ ਲਾਕ ਦੀ ਵਰਤੋਂ ਕਰੋ ਜਿਸਨੂੰ ਭੁੱਲਿਆ ਜਾਂ ਚੋਰੀ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ। ਹੋਰ ਜਾਣੋ"</string>
<string name="choose_provider_title" msgid="7245243990139698508">"<xliff:g id="CREATETYPES">%1$s</xliff:g> ਲਈ ਕੋਈ ਥਾਂ ਚੁਣੋ"</string>
@@ -32,6 +31,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"ਕੀ ਆਪਣੇ ਸਾਰੇ ਸਾਈਨ-ਇਨਾਂ ਲਈ<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ਦੀ ਵਰਤੋਂ ਕਰਨੀ ਹੈ?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
diff --git a/packages/CredentialManager/res/values-pl/strings.xml b/packages/CredentialManager/res/values-pl/strings.xml
index e684f232b00f..af6bc9d22fa8 100644
--- a/packages/CredentialManager/res/values-pl/strings.xml
+++ b/packages/CredentialManager/res/values-pl/strings.xml
@@ -31,6 +31,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Używać usługi <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> w przypadku wszystkich danych logowania?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
diff --git a/packages/CredentialManager/res/values-pt-rBR/strings.xml b/packages/CredentialManager/res/values-pt-rBR/strings.xml
index 570d0e6dbbfa..d950bb45366e 100644
--- a/packages/CredentialManager/res/values-pt-rBR/strings.xml
+++ b/packages/CredentialManager/res/values-pt-rBR/strings.xml
@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for app_name (4539824758261855508) -->
- <skip />
+ <string name="app_name" msgid="4539824758261855508">"Gerenciador de credenciais"</string>
<string name="string_cancel" msgid="6369133483981306063">"Cancelar"</string>
<string name="string_continue" msgid="1346732695941131882">"Continuar"</string>
<string name="string_create_in_another_place" msgid="1033635365843437603">"Criar em outro lugar"</string>
@@ -31,6 +30,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Usar <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> para todos os seus logins?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
@@ -39,8 +40,7 @@
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> senhas, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> chaves de acesso"</string>
<string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> senhas"</string>
<string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> chaves de acesso"</string>
- <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
- <skip />
+ <string name="passkey_before_subtitle" msgid="2448119456208647444">"Chave de acesso"</string>
<string name="another_device" msgid="5147276802037801217">"Outro dispositivo"</string>
<string name="other_password_manager" msgid="565790221427004141">"Outros gerenciadores de senha"</string>
<string name="close_sheet" msgid="1393792015338908262">"Fechar página"</string>
diff --git a/packages/CredentialManager/res/values-pt-rPT/strings.xml b/packages/CredentialManager/res/values-pt-rPT/strings.xml
index 8a105cd1d7b9..c46143cbae51 100644
--- a/packages/CredentialManager/res/values-pt-rPT/strings.xml
+++ b/packages/CredentialManager/res/values-pt-rPT/strings.xml
@@ -11,12 +11,10 @@
<string name="passkey_creation_intro_title" msgid="402553911484409884">"Uma forma simples de iniciar sessão em segurança"</string>
<string name="passkey_creation_intro_body" msgid="7493320456005579290">"Use a sua impressão digital, rosto ou bloqueio de ecrã para iniciar sessão com uma chave de acesso única que não pode ser esquecida nem perdida. Saiba mais"</string>
<string name="choose_provider_title" msgid="7245243990139698508">"Escolha onde quer guardar <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
- <!-- no translation found for create_your_passkeys (8901224153607590596) -->
- <skip />
+ <string name="create_your_passkeys" msgid="8901224153607590596">"criar as suas chaves de acesso"</string>
<string name="save_your_password" msgid="6597736507991704307">"guardar a sua palavra-passe"</string>
<string name="save_your_sign_in_info" msgid="7213978049817076882">"guardar as suas informações de início de sessão"</string>
- <!-- no translation found for choose_provider_body (8045759834416308059) -->
- <skip />
+ <string name="choose_provider_body" msgid="8045759834416308059">"Defina um gestor de palavras-passe predefinido para guardar as suas palavras-passe e chaves de acesso e iniciar sessão mais rapidamente da próxima vez."</string>
<string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Criar uma chave de acesso em <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_password_title" msgid="8812546498357380545">"Guardar a sua palavra-passe em <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Guardar as suas informações de início de sessão em <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
@@ -24,15 +22,12 @@
<string name="passkey" msgid="632353688396759522">"chave de acesso"</string>
<string name="password" msgid="6738570945182936667">"palavra-passe"</string>
<string name="sign_ins" msgid="4710739369149469208">"inícios de sessão"</string>
- <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
- <skip />
- <!-- no translation found for save_password_to_title (3450480045270186421) -->
- <skip />
- <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
- <skip />
+ <string name="create_passkey_in_title" msgid="2714306562710897785">"Criar chave de acesso em"</string>
+ <string name="save_password_to_title" msgid="3450480045270186421">"Guardar palavra-passe em"</string>
+ <string name="save_sign_in_to_title" msgid="8328143607671760232">"Guardar início de sessão em"</string>
+ <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"Criar uma chave de acesso noutro dispositivo?"</string>
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Usar <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> para todos os seus inícios de sessão?"</string>
- <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
- <skip />
+ <string name="use_provider_for_all_description" msgid="6560593199974037820">"Este gestor de palavras-passe armazena as suas palavras-passe e chaves de acesso para ajudar a iniciar sessão facilmente."</string>
<string name="set_as_default" msgid="4415328591568654603">"Definir como predefinição"</string>
<string name="use_once" msgid="9027366575315399714">"Usar uma vez"</string>
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> palavras-passe, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> chaves de acesso"</string>
diff --git a/packages/CredentialManager/res/values-pt/strings.xml b/packages/CredentialManager/res/values-pt/strings.xml
index 570d0e6dbbfa..d950bb45366e 100644
--- a/packages/CredentialManager/res/values-pt/strings.xml
+++ b/packages/CredentialManager/res/values-pt/strings.xml
@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for app_name (4539824758261855508) -->
- <skip />
+ <string name="app_name" msgid="4539824758261855508">"Gerenciador de credenciais"</string>
<string name="string_cancel" msgid="6369133483981306063">"Cancelar"</string>
<string name="string_continue" msgid="1346732695941131882">"Continuar"</string>
<string name="string_create_in_another_place" msgid="1033635365843437603">"Criar em outro lugar"</string>
@@ -31,6 +30,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Usar <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> para todos os seus logins?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
@@ -39,8 +40,7 @@
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> senhas, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> chaves de acesso"</string>
<string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> senhas"</string>
<string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> chaves de acesso"</string>
- <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
- <skip />
+ <string name="passkey_before_subtitle" msgid="2448119456208647444">"Chave de acesso"</string>
<string name="another_device" msgid="5147276802037801217">"Outro dispositivo"</string>
<string name="other_password_manager" msgid="565790221427004141">"Outros gerenciadores de senha"</string>
<string name="close_sheet" msgid="1393792015338908262">"Fechar página"</string>
diff --git a/packages/CredentialManager/res/values-ro/strings.xml b/packages/CredentialManager/res/values-ro/strings.xml
index 51955d4b70f2..6947281390f0 100644
--- a/packages/CredentialManager/res/values-ro/strings.xml
+++ b/packages/CredentialManager/res/values-ro/strings.xml
@@ -31,6 +31,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Folosești <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> pentru toate conectările?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
diff --git a/packages/CredentialManager/res/values-ru/strings.xml b/packages/CredentialManager/res/values-ru/strings.xml
index 2a459c1d98bb..dcc643eee7cd 100644
--- a/packages/CredentialManager/res/values-ru/strings.xml
+++ b/packages/CredentialManager/res/values-ru/strings.xml
@@ -1,15 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for app_name (4539824758261855508) -->
- <skip />
+ <string name="app_name" msgid="4539824758261855508">"Менеджер учетных данных"</string>
<string name="string_cancel" msgid="6369133483981306063">"Отмена"</string>
<string name="string_continue" msgid="1346732695941131882">"Продолжить"</string>
<string name="string_create_in_another_place" msgid="1033635365843437603">"Создать в другом месте"</string>
<string name="string_save_to_another_place" msgid="7590325934591079193">"Сохранить в другом месте"</string>
<string name="string_use_another_device" msgid="8754514926121520445">"Использовать другое устройство"</string>
- <!-- no translation found for string_save_to_another_device (1959562542075194458) -->
- <skip />
+ <string name="string_save_to_another_device" msgid="1959562542075194458">"Сохранить на другом устройстве"</string>
<string name="passkey_creation_intro_title" msgid="402553911484409884">"Простой и безопасный способ входа"</string>
<string name="passkey_creation_intro_body" msgid="7493320456005579290">"С уникальным ключом доступа, который невозможно украсть или забыть, вы можете подтверждать свою личность по отпечатку пальца, с помощью фейсконтроля или блокировки экрана. Подробнее…"</string>
<string name="choose_provider_title" msgid="7245243990139698508">"Выберите, где <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
@@ -32,6 +30,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Всегда входить с помощью приложения \"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>\"?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
@@ -40,8 +40,7 @@
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"Пароли (<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>) и ключи доступа (<xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>)"</string>
<string name="more_options_usage_passwords" msgid="1632047277723187813">"Пароли (<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>)"</string>
<string name="more_options_usage_passkeys" msgid="5390320437243042237">"Ключи доступа (<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g>)"</string>
- <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
- <skip />
+ <string name="passkey_before_subtitle" msgid="2448119456208647444">"Ключ доступа"</string>
<string name="another_device" msgid="5147276802037801217">"Другое устройство"</string>
<string name="other_password_manager" msgid="565790221427004141">"Другие менеджеры паролей"</string>
<string name="close_sheet" msgid="1393792015338908262">"Закрыть лист"</string>
diff --git a/packages/CredentialManager/res/values-si/strings.xml b/packages/CredentialManager/res/values-si/strings.xml
index de5a5a2c95a4..bf885a987cba 100644
--- a/packages/CredentialManager/res/values-si/strings.xml
+++ b/packages/CredentialManager/res/values-si/strings.xml
@@ -8,8 +8,7 @@
<string name="string_create_in_another_place" msgid="1033635365843437603">"වෙනත් ස්ථානයක තනන්න"</string>
<string name="string_save_to_another_place" msgid="7590325934591079193">"වෙනත් ස්ථානයකට සුරකින්න"</string>
<string name="string_use_another_device" msgid="8754514926121520445">"වෙනත් උපාංගයක් භාවිතා කරන්න"</string>
- <!-- no translation found for string_save_to_another_device (1959562542075194458) -->
- <skip />
+ <string name="string_save_to_another_device" msgid="1959562542075194458">"වෙනත් උපාංගයකට සුරකින්න"</string>
<string name="passkey_creation_intro_title" msgid="402553911484409884">"සුරක්ෂිතව පුරනය වීමට සරල ක්‍රමයක්"</string>
<string name="passkey_creation_intro_body" msgid="7493320456005579290">"අමතක කළ නොහැකි හෝ සොරකම් කළ නොහැකි අනන්‍ය මුරයතුරක් සමග පුරනය වීමට ඔබේ ඇඟිලි සලකුණ, මුහුණ හෝ තිර අගුල භාවිතා කරන්න. තව දැන ගන්න⁠"</string>
<string name="choose_provider_title" msgid="7245243990139698508">"<xliff:g id="CREATETYPES">%1$s</xliff:g> කොතැනක ද යන්න තෝරා ගන්න"</string>
@@ -32,6 +31,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"ඔබේ සියලු පුරනය වීම් සඳහා <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> භාවිතා කරන්න ද?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
diff --git a/packages/CredentialManager/res/values-sk/strings.xml b/packages/CredentialManager/res/values-sk/strings.xml
index 4545868ba76c..1c73c57d0421 100644
--- a/packages/CredentialManager/res/values-sk/strings.xml
+++ b/packages/CredentialManager/res/values-sk/strings.xml
@@ -1,24 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for app_name (4539824758261855508) -->
- <skip />
+ <string name="app_name" msgid="4539824758261855508">"Správca prihlasovacích údajov"</string>
<string name="string_cancel" msgid="6369133483981306063">"Zrušiť"</string>
<string name="string_continue" msgid="1346732695941131882">"Pokračovať"</string>
<string name="string_create_in_another_place" msgid="1033635365843437603">"Vytvoriť inde"</string>
<string name="string_save_to_another_place" msgid="7590325934591079193">"Uložiť inde"</string>
<string name="string_use_another_device" msgid="8754514926121520445">"Použiť iné zariadenie"</string>
- <!-- no translation found for string_save_to_another_device (1959562542075194458) -->
- <skip />
+ <string name="string_save_to_another_device" msgid="1959562542075194458">"Uložiť do iného zariadenia"</string>
<string name="passkey_creation_intro_title" msgid="402553911484409884">"Jednoduchý spôsob bezpečného prihlasovania"</string>
<string name="passkey_creation_intro_body" msgid="7493320456005579290">"Použite odtlačok prsta, tvár alebo zámku obrazovky a prihláste sa jedinečným prístupovým kľúčom, ktorý sa nedá zabudnúť ani ukradnúť. Ďalšie informácie"</string>
<string name="choose_provider_title" msgid="7245243990139698508">"Vyberte, kam <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
- <!-- no translation found for create_your_passkeys (8901224153607590596) -->
- <skip />
+ <string name="create_your_passkeys" msgid="8901224153607590596">"vytvoriť prístupové kľúče"</string>
<string name="save_your_password" msgid="6597736507991704307">"uložiť heslo"</string>
<string name="save_your_sign_in_info" msgid="7213978049817076882">"uložiť prihlasovacie údaje"</string>
- <!-- no translation found for choose_provider_body (8045759834416308059) -->
- <skip />
+ <string name="choose_provider_body" msgid="8045759834416308059">"Nastavte predvoleného správcu hesiel, aby ukladal vaše heslá aj prístupové kľúče, a nabudúce sa prihláste rýchlejšie."</string>
<string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Chcete vytvoriť prístupový kľúč v službe <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_password_title" msgid="8812546498357380545">"Chcete uložiť heslo do služby <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Chcete uložiť svoje prihlasovacie údaje do služby <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
@@ -26,22 +22,18 @@
<string name="passkey" msgid="632353688396759522">"prístupový kľúč"</string>
<string name="password" msgid="6738570945182936667">"heslo"</string>
<string name="sign_ins" msgid="4710739369149469208">"prihlasovacie údaje"</string>
- <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
- <skip />
- <!-- no translation found for save_password_to_title (3450480045270186421) -->
- <skip />
- <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
- <skip />
+ <string name="create_passkey_in_title" msgid="2714306562710897785">"Vytvorenie prístupového kľúča v umiestnení"</string>
+ <string name="save_password_to_title" msgid="3450480045270186421">"Uloženie hesla do umiestnenia"</string>
+ <string name="save_sign_in_to_title" msgid="8328143607671760232">"Uloženie prihlasovacích údajov do umiestnenia"</string>
+ <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"Chcete vytvoriť prístupový kľúč v inom zariadení?"</string>
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Chcete pre všetky svoje prihlasovacie údaje použiť <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
- <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
- <skip />
+ <string name="use_provider_for_all_description" msgid="6560593199974037820">"Tento správca hesiel uchová vaše heslá a prístupové kľúče, aby vám pomohol ľahšie sa prihlasovať."</string>
<string name="set_as_default" msgid="4415328591568654603">"Nastaviť ako predvolené"</string>
<string name="use_once" msgid="9027366575315399714">"Použiť raz"</string>
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"Počet hesiel: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>, počet prístupových kľúčov <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>"</string>
<string name="more_options_usage_passwords" msgid="1632047277723187813">"Počet hesiel: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>"</string>
<string name="more_options_usage_passkeys" msgid="5390320437243042237">"Počet prístupových kľúčov: <xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g>"</string>
- <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
- <skip />
+ <string name="passkey_before_subtitle" msgid="2448119456208647444">"Prístupový kľúč"</string>
<string name="another_device" msgid="5147276802037801217">"Iné zariadenie"</string>
<string name="other_password_manager" msgid="565790221427004141">"Iní správcovia hesiel"</string>
<string name="close_sheet" msgid="1393792015338908262">"Zavrieť hárok"</string>
diff --git a/packages/CredentialManager/res/values-sl/strings.xml b/packages/CredentialManager/res/values-sl/strings.xml
index 94edf669434a..969f29032837 100644
--- a/packages/CredentialManager/res/values-sl/strings.xml
+++ b/packages/CredentialManager/res/values-sl/strings.xml
@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for app_name (4539824758261855508) -->
- <skip />
+ <string name="app_name" msgid="4539824758261855508">"Upravitelj poverilnic"</string>
<string name="string_cancel" msgid="6369133483981306063">"Prekliči"</string>
<string name="string_continue" msgid="1346732695941131882">"Naprej"</string>
<string name="string_create_in_another_place" msgid="1033635365843437603">"Ustvarjanje na drugem mestu"</string>
@@ -31,6 +30,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Želite za vse prijave uporabiti »<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>«?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
@@ -39,8 +40,7 @@
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"Št. gesel: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>, št. ključev za dostop: <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>"</string>
<string name="more_options_usage_passwords" msgid="1632047277723187813">"Št. gesel: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>"</string>
<string name="more_options_usage_passkeys" msgid="5390320437243042237">"Št. ključev za dostop: <xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g>"</string>
- <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
- <skip />
+ <string name="passkey_before_subtitle" msgid="2448119456208647444">"Ključ za dostop"</string>
<string name="another_device" msgid="5147276802037801217">"Druga naprava"</string>
<string name="other_password_manager" msgid="565790221427004141">"Drugi upravitelji gesel"</string>
<string name="close_sheet" msgid="1393792015338908262">"Zapri list"</string>
diff --git a/packages/CredentialManager/res/values-sq/strings.xml b/packages/CredentialManager/res/values-sq/strings.xml
index 6b85a90a1e0c..bce068309839 100644
--- a/packages/CredentialManager/res/values-sq/strings.xml
+++ b/packages/CredentialManager/res/values-sq/strings.xml
@@ -8,8 +8,7 @@
<string name="string_create_in_another_place" msgid="1033635365843437603">"Krijo në një vend tjetër"</string>
<string name="string_save_to_another_place" msgid="7590325934591079193">"Ruaj në një vend tjetër"</string>
<string name="string_use_another_device" msgid="8754514926121520445">"Përdor një pajisje tjetër"</string>
- <!-- no translation found for string_save_to_another_device (1959562542075194458) -->
- <skip />
+ <string name="string_save_to_another_device" msgid="1959562542075194458">"Ruaj në një pajisje tjetër"</string>
<string name="passkey_creation_intro_title" msgid="402553911484409884">"Një mënyrë e thjeshtë për t\'u identifikuar në mënyrë të sigurt"</string>
<string name="passkey_creation_intro_body" msgid="7493320456005579290">"Përdor gjurmën e gishtit, fytyrën ose kyçjen e ekranit për t\'u identifikuar me një çelës unik kalimi i cili nuk mund të harrohet ose të vidhet. Mëso më shumë"</string>
<string name="choose_provider_title" msgid="7245243990139698508">"Zgjidh se ku të <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
@@ -32,6 +31,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Të përdoret <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> për të gjitha identifikimet?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
diff --git a/packages/CredentialManager/res/values-sr/strings.xml b/packages/CredentialManager/res/values-sr/strings.xml
index 79c2eef82b6f..6a5235c79d56 100644
--- a/packages/CredentialManager/res/values-sr/strings.xml
+++ b/packages/CredentialManager/res/values-sr/strings.xml
@@ -31,6 +31,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Желите да за сва пријављивања користите: <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
diff --git a/packages/CredentialManager/res/values-sv/strings.xml b/packages/CredentialManager/res/values-sv/strings.xml
index 7b250561e077..a4fffb979b8f 100644
--- a/packages/CredentialManager/res/values-sv/strings.xml
+++ b/packages/CredentialManager/res/values-sv/strings.xml
@@ -1,15 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for app_name (4539824758261855508) -->
- <skip />
+ <string name="app_name" msgid="4539824758261855508">"Credential Manager"</string>
<string name="string_cancel" msgid="6369133483981306063">"Avbryt"</string>
<string name="string_continue" msgid="1346732695941131882">"Fortsätt"</string>
<string name="string_create_in_another_place" msgid="1033635365843437603">"Skapa på en annan plats"</string>
<string name="string_save_to_another_place" msgid="7590325934591079193">"Spara på en annan plats"</string>
<string name="string_use_another_device" msgid="8754514926121520445">"Använd en annan enhet"</string>
- <!-- no translation found for string_save_to_another_device (1959562542075194458) -->
- <skip />
+ <string name="string_save_to_another_device" msgid="1959562542075194458">"Spara på en annan enhet"</string>
<string name="passkey_creation_intro_title" msgid="402553911484409884">"Ett enkelt sätt att logga in säkert på"</string>
<string name="passkey_creation_intro_body" msgid="7493320456005579290">"Använd ditt fingeravtryck, ansikte eller skärmlås om du vill logga in med en unik nyckel som inte kan glömmas bort eller bli stulen. Läs mer"</string>
<string name="choose_provider_title" msgid="7245243990139698508">"Välj var du <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
@@ -32,6 +30,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Vill du använda <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> för alla dina inloggningar?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
@@ -40,8 +40,7 @@
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> lösenord, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> nycklar"</string>
<string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> lösenord"</string>
<string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> nycklar"</string>
- <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
- <skip />
+ <string name="passkey_before_subtitle" msgid="2448119456208647444">"Nyckel"</string>
<string name="another_device" msgid="5147276802037801217">"En annan enhet"</string>
<string name="other_password_manager" msgid="565790221427004141">"Andra lösenordshanterare"</string>
<string name="close_sheet" msgid="1393792015338908262">"Stäng kalkylarket"</string>
diff --git a/packages/CredentialManager/res/values-sw/strings.xml b/packages/CredentialManager/res/values-sw/strings.xml
new file mode 100644
index 000000000000..bfd10747554c
--- /dev/null
+++ b/packages/CredentialManager/res/values-sw/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for app_name (4539824758261855508) -->
+ <skip />
+ <string name="string_cancel" msgid="6369133483981306063">"Ghairi"</string>
+ <string name="string_continue" msgid="1346732695941131882">"Endelea"</string>
+ <string name="string_create_in_another_place" msgid="1033635365843437603">"Unda katika sehemu nyingine"</string>
+ <string name="string_save_to_another_place" msgid="7590325934591079193">"Hifadhi sehemu nyingine"</string>
+ <string name="string_use_another_device" msgid="8754514926121520445">"Tumia kifaa kingine"</string>
+ <string name="string_save_to_another_device" msgid="1959562542075194458">"Hifadhi kwenye kifaa kingine"</string>
+ <string name="passkey_creation_intro_title" msgid="402553911484409884">"Njia rahisi ya kuingia katika akaunti kwa usalama"</string>
+ <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Tumia alama ya vidole, uso au kipengele cha kufunga skrini ili uingie katika kaunti kwa kutumia nenosiri la kipekee ambalo haliwezi kusahaulika au kuibiwa. Pata maelezo zaidi"</string>
+ <string name="choose_provider_title" msgid="7245243990139698508">"Chagua mahali pa <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+ <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+ <skip />
+ <string name="save_your_password" msgid="6597736507991704307">"hifadhi nenosiri lako"</string>
+ <string name="save_your_sign_in_info" msgid="7213978049817076882">"hifadhi maelezo yako ya kuingia katika akaunti"</string>
+ <!-- no translation found for choose_provider_body (8045759834416308059) -->
+ <skip />
+ <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Ungependa kuunda ufunguo wa siri katika <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_option_password_title" msgid="8812546498357380545">"Ungependa kuhifadhi nenosiri lako kwenye <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Ungependa kuhifadhi maelezo yako ya kuingia katika akaunti kwenye <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_option_description" msgid="4419171903963100257">"Unaweza kutumia <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> yako ya <xliff:g id="TYPE">%2$s</xliff:g> kwenye kifaa chochote. Imehifadhiwa kwenye <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> kwa ajili ya <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string>
+ <string name="passkey" msgid="632353688396759522">"ufunguo wa siri"</string>
+ <string name="password" msgid="6738570945182936667">"nenosiri"</string>
+ <string name="sign_ins" msgid="4710739369149469208">"michakato ya kuingia katika akaunti"</string>
+ <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
+ <skip />
+ <!-- no translation found for save_password_to_title (3450480045270186421) -->
+ <skip />
+ <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
+ <skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
+ <string name="use_provider_for_all_title" msgid="4201020195058980757">"Ungependa kutumia <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> kwa ajili ya michakato yako yote ya kuingia katika akaunti?"</string>
+ <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
+ <skip />
+ <string name="set_as_default" msgid="4415328591568654603">"Weka iwe chaguomsingi"</string>
+ <string name="use_once" msgid="9027366575315399714">"Tumia mara moja"</string>
+ <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"Manenosiri <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>, funguo <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> za siri"</string>
+ <string name="more_options_usage_passwords" msgid="1632047277723187813">"Manenosiri <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>"</string>
+ <string name="more_options_usage_passkeys" msgid="5390320437243042237">"Funguo <xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> za siri"</string>
+ <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
+ <skip />
+ <string name="another_device" msgid="5147276802037801217">"Kifaa kingine"</string>
+ <string name="other_password_manager" msgid="565790221427004141">"Vidhibiti vinginevyo vya manenosiri"</string>
+ <string name="close_sheet" msgid="1393792015338908262">"Funga laha"</string>
+ <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Rudi kwenye ukurasa uliotangulia"</string>
+ <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Ungependa kutumia ufunguo wa siri uliohifadhiwa wa<xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+ <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Ungependa kutumia kitambulisho kilichohifadhiwa cha kuingia katika akaunti ya <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+ <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Chagua vitambulisho vilivyohifadhiwa kwa ajili ya kuingia katika akaunti ya <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Ingia katika akaunti kwa kutumia njia nyingine"</string>
+ <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Hapana"</string>
+ <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Endelea"</string>
+ <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Chaguo za kuingia katika akaunti"</string>
+ <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Kwa ajili ya <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
+ <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Vidhibiti vya manenosiri vilivyofungwa"</string>
+ <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Gusa ili ufungue"</string>
+ <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Dhibiti michakato ya kuingia katika akaunti"</string>
+ <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Kutoka kwenye kifaa kingine"</string>
+ <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Tumia kifaa tofauti"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-ta/strings.xml b/packages/CredentialManager/res/values-ta/strings.xml
index 646c4699e215..10c52594c7d3 100644
--- a/packages/CredentialManager/res/values-ta/strings.xml
+++ b/packages/CredentialManager/res/values-ta/strings.xml
@@ -31,6 +31,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"உங்கள் அனைத்து உள்நுழைவுகளுக்கும் <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>ஐப் பயன்படுத்தவா?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
diff --git a/packages/CredentialManager/res/values-te/strings.xml b/packages/CredentialManager/res/values-te/strings.xml
index d94f3d35e87b..f7617b3e34dd 100644
--- a/packages/CredentialManager/res/values-te/strings.xml
+++ b/packages/CredentialManager/res/values-te/strings.xml
@@ -31,6 +31,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"మీ అన్ని సైన్-ఇన్ వివరాల కోసం <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>ను ఉపయోగించాలా?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
diff --git a/packages/CredentialManager/res/values-th/strings.xml b/packages/CredentialManager/res/values-th/strings.xml
index 43f3f0fcd2bc..d70e94ad0425 100644
--- a/packages/CredentialManager/res/values-th/strings.xml
+++ b/packages/CredentialManager/res/values-th/strings.xml
@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for app_name (4539824758261855508) -->
- <skip />
+ <string name="app_name" msgid="4539824758261855508">"เครื่องมือจัดการข้อมูลเข้าสู่ระบบ"</string>
<string name="string_cancel" msgid="6369133483981306063">"ยกเลิก"</string>
<string name="string_continue" msgid="1346732695941131882">"ต่อไป"</string>
<string name="string_create_in_another_place" msgid="1033635365843437603">"สร้างในตำแหน่งอื่น"</string>
@@ -31,6 +30,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"ใช้ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> สำหรับการลงชื่อเข้าใช้ทั้งหมดใช่ไหม"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
@@ -39,8 +40,7 @@
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"รหัสผ่าน <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> รายการ พาสคีย์ <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> รายการ"</string>
<string name="more_options_usage_passwords" msgid="1632047277723187813">"รหัสผ่าน <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> รายการ"</string>
<string name="more_options_usage_passkeys" msgid="5390320437243042237">"พาสคีย์ <xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> รายการ"</string>
- <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
- <skip />
+ <string name="passkey_before_subtitle" msgid="2448119456208647444">"พาสคีย์"</string>
<string name="another_device" msgid="5147276802037801217">"อุปกรณ์อื่น"</string>
<string name="other_password_manager" msgid="565790221427004141">"เครื่องมือจัดการรหัสผ่านอื่นๆ"</string>
<string name="close_sheet" msgid="1393792015338908262">"ปิดชีต"</string>
diff --git a/packages/CredentialManager/res/values-tl/strings.xml b/packages/CredentialManager/res/values-tl/strings.xml
index 4dae03722dcc..01fd2f02063d 100644
--- a/packages/CredentialManager/res/values-tl/strings.xml
+++ b/packages/CredentialManager/res/values-tl/strings.xml
@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for app_name (4539824758261855508) -->
- <skip />
+ <string name="app_name" msgid="4539824758261855508">"Manager ng Kredensyal"</string>
<string name="string_cancel" msgid="6369133483981306063">"Kanselahin"</string>
<string name="string_continue" msgid="1346732695941131882">"Magpatuloy"</string>
<string name="string_create_in_another_place" msgid="1033635365843437603">"Gumawa sa ibang lugar"</string>
@@ -31,6 +30,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Gamitin ang <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> para sa lahat ng iyong pag-sign in?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
@@ -39,8 +40,7 @@
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> (na) password, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> (na) passkey"</string>
<string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> (na) password"</string>
<string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> (na) passkey"</string>
- <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
- <skip />
+ <string name="passkey_before_subtitle" msgid="2448119456208647444">"Passkey"</string>
<string name="another_device" msgid="5147276802037801217">"Ibang device"</string>
<string name="other_password_manager" msgid="565790221427004141">"Iba pang password manager"</string>
<string name="close_sheet" msgid="1393792015338908262">"Isara ang sheet"</string>
diff --git a/packages/CredentialManager/res/values-tr/strings.xml b/packages/CredentialManager/res/values-tr/strings.xml
index c1ccd984b375..30ed43e6ed19 100644
--- a/packages/CredentialManager/res/values-tr/strings.xml
+++ b/packages/CredentialManager/res/values-tr/strings.xml
@@ -8,7 +8,7 @@
<string name="string_create_in_another_place" msgid="1033635365843437603">"Başka bir yerde oluşturun"</string>
<string name="string_save_to_another_place" msgid="7590325934591079193">"Başka bir yere kaydedin"</string>
<string name="string_use_another_device" msgid="8754514926121520445">"Başka bir cihaz kullan"</string>
- <string name="string_save_to_another_device" msgid="1959562542075194458">"Başka bir cihaza kaydedin"</string>
+ <string name="string_save_to_another_device" msgid="1959562542075194458">"Başka bir cihaza kaydet"</string>
<string name="passkey_creation_intro_title" msgid="402553911484409884">"Güvenli bir şekilde oturum açmanın basit yolu"</string>
<string name="passkey_creation_intro_body" msgid="7493320456005579290">"Parmak iziniz, yüzünüz ya da ekran kilidinizi kullanarak unutması veya çalınması mümkün olmayan benzersiz bir şifre anahtarıyla oturum açın. Daha fazla bilgi"</string>
<string name="choose_provider_title" msgid="7245243990139698508">"<xliff:g id="CREATETYPES">%1$s</xliff:g> yerini seçin"</string>
@@ -31,6 +31,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Tüm oturum açma işlemlerinizde <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> kullanılsın mı?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
diff --git a/packages/CredentialManager/res/values-uk/strings.xml b/packages/CredentialManager/res/values-uk/strings.xml
index 396da4d1da87..69d4612eb594 100644
--- a/packages/CredentialManager/res/values-uk/strings.xml
+++ b/packages/CredentialManager/res/values-uk/strings.xml
@@ -8,8 +8,7 @@
<string name="string_create_in_another_place" msgid="1033635365843437603">"Створити в іншому місці"</string>
<string name="string_save_to_another_place" msgid="7590325934591079193">"Зберегти в іншому місці"</string>
<string name="string_use_another_device" msgid="8754514926121520445">"Скористатись іншим пристроєм"</string>
- <!-- no translation found for string_save_to_another_device (1959562542075194458) -->
- <skip />
+ <string name="string_save_to_another_device" msgid="1959562542075194458">"Зберегти на іншому пристрої"</string>
<string name="passkey_creation_intro_title" msgid="402553911484409884">"Зручний спосіб для безпечного входу"</string>
<string name="passkey_creation_intro_body" msgid="7493320456005579290">"Користуйтеся відбитком пальця, фейсконтролем або іншим способом розблокування екрана, щоб входити в обліковий запис за допомогою унікального ключа доступу, який неможливо забути чи викрасти. Докладніше"</string>
<string name="choose_provider_title" msgid="7245243990139698508">"Виберіть, де <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
@@ -32,6 +31,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Використовувати сервіс <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> в усіх випадках входу?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
diff --git a/packages/CredentialManager/res/values-ur/strings.xml b/packages/CredentialManager/res/values-ur/strings.xml
index e67b94c7853b..2d6607934bc0 100644
--- a/packages/CredentialManager/res/values-ur/strings.xml
+++ b/packages/CredentialManager/res/values-ur/strings.xml
@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for app_name (4539824758261855508) -->
- <skip />
+ <string name="app_name" msgid="4539824758261855508">"سند سے متعلق مینیجر"</string>
<string name="string_cancel" msgid="6369133483981306063">"منسوخ کریں"</string>
<string name="string_continue" msgid="1346732695941131882">"جاری رکھیں"</string>
<string name="string_create_in_another_place" msgid="1033635365843437603">"دوسرے مقام میں تخلیق کریں"</string>
@@ -31,6 +30,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"اپنے سبھی سائن انز کے لیے <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> کا استعمال کریں؟"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
@@ -39,8 +40,7 @@
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> پاس ورڈز، <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> پاس کیز"</string>
<string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> پاس ورڈز"</string>
<string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> پاس کیز"</string>
- <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
- <skip />
+ <string name="passkey_before_subtitle" msgid="2448119456208647444">"پاس کی"</string>
<string name="another_device" msgid="5147276802037801217">"دوسرا آلہ"</string>
<string name="other_password_manager" msgid="565790221427004141">"دیگر پاس ورڈ مینیجرز"</string>
<string name="close_sheet" msgid="1393792015338908262">"شیٹ بند کریں"</string>
diff --git a/packages/CredentialManager/res/values-uz/strings.xml b/packages/CredentialManager/res/values-uz/strings.xml
index 6c3e211b9d6a..4ac35b2e6f7d 100644
--- a/packages/CredentialManager/res/values-uz/strings.xml
+++ b/packages/CredentialManager/res/values-uz/strings.xml
@@ -31,6 +31,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Hamma kirishlarda <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ishlatilsinmi?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
diff --git a/packages/CredentialManager/res/values-vi/strings.xml b/packages/CredentialManager/res/values-vi/strings.xml
index d4703f394c89..fd5b986c574b 100644
--- a/packages/CredentialManager/res/values-vi/strings.xml
+++ b/packages/CredentialManager/res/values-vi/strings.xml
@@ -31,6 +31,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Dùng <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> cho mọi thông tin đăng nhập của bạn?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
diff --git a/packages/CredentialManager/res/values-zh-rCN/strings.xml b/packages/CredentialManager/res/values-zh-rCN/strings.xml
index 145eac2701ba..a14dd2f6fe20 100644
--- a/packages/CredentialManager/res/values-zh-rCN/strings.xml
+++ b/packages/CredentialManager/res/values-zh-rCN/strings.xml
@@ -31,6 +31,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"将“<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>”用于您的所有登录信息?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
diff --git a/packages/CredentialManager/res/values-zh-rHK/strings.xml b/packages/CredentialManager/res/values-zh-rHK/strings.xml
index f277c22e92c6..71dfa1affb72 100644
--- a/packages/CredentialManager/res/values-zh-rHK/strings.xml
+++ b/packages/CredentialManager/res/values-zh-rHK/strings.xml
@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for app_name (4539824758261855508) -->
- <skip />
+ <string name="app_name" msgid="4539824758261855508">"憑證管理工具"</string>
<string name="string_cancel" msgid="6369133483981306063">"取消"</string>
<string name="string_continue" msgid="1346732695941131882">"繼續"</string>
<string name="string_create_in_another_place" msgid="1033635365843437603">"在其他位置建立"</string>
@@ -31,6 +30,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"要將「<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>」用於所有的登入資料嗎?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
@@ -39,8 +40,7 @@
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> 個密碼,<xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> 個密鑰"</string>
<string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> 個密碼"</string>
<string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> 個密鑰"</string>
- <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
- <skip />
+ <string name="passkey_before_subtitle" msgid="2448119456208647444">"密碼金鑰"</string>
<string name="another_device" msgid="5147276802037801217">"其他裝置"</string>
<string name="other_password_manager" msgid="565790221427004141">"其他密碼管理工具"</string>
<string name="close_sheet" msgid="1393792015338908262">"閂工作表"</string>
diff --git a/packages/CredentialManager/res/values-zh-rTW/strings.xml b/packages/CredentialManager/res/values-zh-rTW/strings.xml
index fd1dfc8e29e1..0d636c24cca2 100644
--- a/packages/CredentialManager/res/values-zh-rTW/strings.xml
+++ b/packages/CredentialManager/res/values-zh-rTW/strings.xml
@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for app_name (4539824758261855508) -->
- <skip />
+ <string name="app_name" msgid="4539824758261855508">"憑證管理工具"</string>
<string name="string_cancel" msgid="6369133483981306063">"取消"</string>
<string name="string_continue" msgid="1346732695941131882">"繼續"</string>
<string name="string_create_in_another_place" msgid="1033635365843437603">"在其他位置建立"</string>
@@ -31,6 +30,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"要將「<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>」用於所有的登入資訊嗎?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
@@ -39,8 +40,7 @@
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> 個密碼,<xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> 個密碼金鑰"</string>
<string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> 個密碼"</string>
<string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> 個密碼金鑰"</string>
- <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
- <skip />
+ <string name="passkey_before_subtitle" msgid="2448119456208647444">"密碼金鑰"</string>
<string name="another_device" msgid="5147276802037801217">"其他裝置"</string>
<string name="other_password_manager" msgid="565790221427004141">"其他密碼管理工具"</string>
<string name="close_sheet" msgid="1393792015338908262">"關閉功能表"</string>
diff --git a/packages/CredentialManager/res/values-zu/strings.xml b/packages/CredentialManager/res/values-zu/strings.xml
index fd2b83e7bfcc..a35c6d2af737 100644
--- a/packages/CredentialManager/res/values-zu/strings.xml
+++ b/packages/CredentialManager/res/values-zu/strings.xml
@@ -31,6 +31,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Sebenzisa i-<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> kukho konke ukungena kwakho ngemvume?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml
index 8c9023c55eff..870d9832ca0b 100644
--- a/packages/CredentialManager/res/values/strings.xml
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -18,9 +18,13 @@
<!-- This appears as a text button where users can click to save this credential to another device. [CHAR LIMIT=80] -->
<string name="string_save_to_another_device">Save to another device</string>
<!-- This appears as the title of the modal bottom sheet introducing what is passkey to users. [CHAR LIMIT=200] -->
- <string name="passkey_creation_intro_title">A simple way to sign in safely</string>
- <!-- This appears as the description body of the modal bottom sheet introducing what is passkey to users. [CHAR LIMIT=200] -->
- <string name="passkey_creation_intro_body">Use your fingerprint, face or screen lock to sign in with a unique passkey that can’t be forgotten or stolen. Learn more</string>
+ <string name="passkey_creation_intro_title">Safer with passkeys</string>
+ <!-- This appears as the description body of the modal bottom sheet introducing why passkey beneficial on the passwords side. [CHAR LIMIT=200] -->
+ <string name="passkey_creation_intro_body_password">No need to create or remember complex passwords</string>
+ <!-- This appears as the description body of the modal bottom sheet introducing why passkey beneficial on the safety side. [CHAR LIMIT=200] -->
+ <string name="passkey_creation_intro_body_fingerprint">Use your fingerprint, face, or screen lock to create a unique passkey</string>
+ <!-- This appears as the description body of the modal bottom sheet introducing why passkey beneficial on the using other devices side. [CHAR LIMIT=200] -->
+ <string name="passkey_creation_intro_body_device">Passkeys are saved to a password manager, so you can sign in on other devices</string>
<!-- This appears as the title of the modal bottom sheet which provides all available providers for users to choose. [CHAR LIMIT=200] -->
<string name="choose_provider_title">Choose where to <xliff:g id="createTypes" example="create your passkeys">%1$s</xliff:g></string>
<!-- Create types which are inserted as a placeholder for string choose_provider_title. [CHAR LIMIT=200] -->
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index e3ed3d925566..f801ba61e073 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -40,12 +40,9 @@ import android.os.Binder
import android.os.Bundle
import android.os.ResultReceiver
import android.service.credentials.CredentialProviderService
-import com.android.credentialmanager.createflow.ActiveEntry
import com.android.credentialmanager.createflow.CreateCredentialUiState
-import com.android.credentialmanager.createflow.CreateScreenState
import com.android.credentialmanager.createflow.EnabledProviderInfo
import com.android.credentialmanager.createflow.RemoteInfo
-import com.android.credentialmanager.createflow.RequestDisplayInfo
import com.android.credentialmanager.getflow.GetCredentialUiState
import com.android.credentialmanager.getflow.GetScreenState
import com.android.credentialmanager.jetpack.developer.CreatePasswordRequest.Companion.toBundle
@@ -128,7 +125,7 @@ class CredentialManagerRepo(
// TODO: handle runtime cast error
providerEnabledList as List<GetCredentialProviderData>, context)
// TODO: covert from real requestInfo
- val requestDisplayInfo = com.android.credentialmanager.getflow.RequestDisplayInfo("tribank")
+ val requestDisplayInfo = com.android.credentialmanager.getflow.RequestDisplayInfo("the app")
return GetCredentialUiState(
providerEnabledList,
GetScreenState.PRIMARY_SELECTION,
@@ -146,20 +143,30 @@ class CredentialManagerRepo(
providerDisabledList, context)
var defaultProvider: EnabledProviderInfo? = null
var remoteEntry: RemoteInfo? = null
+ var createOptionSize = 0
+ var lastSeenProviderWithNonEmptyCreateOptions: EnabledProviderInfo? = null
providerEnabledList.forEach{providerInfo -> providerInfo.createOptions =
providerInfo.createOptions.sortedWith(compareBy { it.lastUsedTimeMillis }).reversed()
if (providerInfo.isDefault) {defaultProvider = providerInfo}
if (providerInfo.remoteEntry != null) {
remoteEntry = providerInfo.remoteEntry!!
}
+ if (providerInfo.createOptions.isNotEmpty()) {
+ createOptionSize += providerInfo.createOptions.size
+ lastSeenProviderWithNonEmptyCreateOptions = providerInfo
+ }
}
return CreateCredentialUiState(
enabledProviders = providerEnabledList,
disabledProviders = providerDisabledList,
- toCreateScreenState(requestDisplayInfo, defaultProvider, remoteEntry),
+ CreateFlowUtils.toCreateScreenState(
+ createOptionSize, false,
+ requestDisplayInfo, defaultProvider, remoteEntry),
requestDisplayInfo,
false,
- toActiveEntry(defaultProvider, remoteEntry),
+ CreateFlowUtils.toActiveEntry(
+ /*defaultProvider=*/defaultProvider, createOptionSize,
+ lastSeenProviderWithNonEmptyCreateOptions, remoteEntry),
)
}
@@ -194,6 +201,7 @@ class CredentialManagerRepo(
.setRemoteEntry(
newRemoteEntry("key2", "subkey-1")
)
+ .setIsDefaultProvider(true)
.build(),
CreateCredentialProviderData
.Builder("com.dashlane")
@@ -510,45 +518,11 @@ class CredentialManagerRepo(
GetCredentialRequest.Builder()
.addGetCredentialOption(
GetCredentialOption(
- TYPE_PUBLIC_KEY_CREDENTIAL, Bundle(), /*requireSystemProvider=*/ false)
+ TYPE_PUBLIC_KEY_CREDENTIAL, Bundle(), Bundle(), /*requireSystemProvider=*/ false)
)
.build(),
/*isFirstUsage=*/false,
"tribank.us"
)
}
-
- private fun toCreateScreenState(
- requestDisplayInfo: RequestDisplayInfo,
- defaultProvider: EnabledProviderInfo?,
- remoteEntry: RemoteInfo?,
- ): CreateScreenState {
- return if (
- defaultProvider != null && defaultProvider.createOptions.isEmpty() && remoteEntry != null
- ){
- CreateScreenState.EXTERNAL_ONLY_SELECTION
- } else if (defaultProvider == null || defaultProvider.createOptions.isEmpty()) {
- if (requestDisplayInfo.type == TYPE_PUBLIC_KEY_CREDENTIAL) {
- CreateScreenState.PASSKEY_INTRO
- } else {
- CreateScreenState.PROVIDER_SELECTION
- }
- } else {
- CreateScreenState.CREATION_OPTION_SELECTION
- }
- }
-
- private fun toActiveEntry(
- defaultProvider: EnabledProviderInfo?,
- remoteEntry: RemoteInfo?,
- ): ActiveEntry? {
- return if (
- defaultProvider != null && defaultProvider.createOptions.isNotEmpty()
- ) {
- ActiveEntry(defaultProvider, defaultProvider.createOptions.first())
- } else if (
- defaultProvider != null && defaultProvider.createOptions.isEmpty() && remoteEntry != null) {
- ActiveEntry(defaultProvider, remoteEntry)
- } else null
- }
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
index d324f875c85a..6a4c599f682b 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
@@ -69,6 +69,7 @@ class CredentialSelectorActivity : ComponentActivity() {
)
providerActivityResult.value?.let {
viewModel.onProviderActivityResult(it)
+ providerActivityResult.value = null
}
CreateCredentialScreen(viewModel = viewModel, providerActivityLauncher = launcher)
}
@@ -80,6 +81,7 @@ class CredentialSelectorActivity : ComponentActivity() {
)
providerActivityResult.value?.let {
viewModel.onProviderActivityResult(it)
+ providerActivityResult.value = null
}
GetCredentialScreen(viewModel = viewModel, providerActivityLauncher = launcher)
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
index 357c55dc2770..0d7e81984ead 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -28,6 +28,9 @@ import android.graphics.drawable.Drawable
import com.android.credentialmanager.createflow.CreateOptionInfo
import com.android.credentialmanager.createflow.RemoteInfo
import com.android.credentialmanager.createflow.RequestDisplayInfo
+import com.android.credentialmanager.createflow.EnabledProviderInfo
+import com.android.credentialmanager.createflow.CreateScreenState
+import com.android.credentialmanager.createflow.ActiveEntry
import com.android.credentialmanager.getflow.ActionEntryInfo
import com.android.credentialmanager.getflow.AuthenticationEntryInfo
import com.android.credentialmanager.getflow.CredentialEntryInfo
@@ -36,6 +39,7 @@ import com.android.credentialmanager.getflow.RemoteEntryInfo
import com.android.credentialmanager.jetpack.developer.CreateCredentialRequest
import com.android.credentialmanager.jetpack.developer.CreatePasswordRequest
import com.android.credentialmanager.jetpack.developer.CreatePublicKeyCredentialRequest
+import com.android.credentialmanager.jetpack.developer.PublicKeyCredential.Companion.TYPE_PUBLIC_KEY_CREDENTIAL
import com.android.credentialmanager.jetpack.provider.ActionUi
import com.android.credentialmanager.jetpack.provider.CredentialEntryUi
import com.android.credentialmanager.jetpack.provider.SaveEntryUi
@@ -243,7 +247,8 @@ class CreateFlowUtils {
createCredentialRequestJetpack.password,
createCredentialRequestJetpack.type,
requestInfo.appPackageName,
- context.getDrawable(R.drawable.ic_password)!!
+ context.getDrawable(R.drawable.ic_password)!!,
+ requestInfo.isFirstUsage
)
}
is CreatePublicKeyCredentialRequest -> {
@@ -261,7 +266,8 @@ class CreateFlowUtils {
displayName,
createCredentialRequestJetpack.type,
requestInfo.appPackageName,
- context.getDrawable(R.drawable.ic_passkey)!!)
+ context.getDrawable(R.drawable.ic_passkey)!!,
+ requestInfo.isFirstUsage)
}
// TODO: correctly parsing for other sign-ins
else -> {
@@ -270,11 +276,58 @@ class CreateFlowUtils {
"Elisa Beckett",
"other-sign-ins",
requestInfo.appPackageName,
- context.getDrawable(R.drawable.ic_other_sign_in)!!)
+ context.getDrawable(R.drawable.ic_other_sign_in)!!,
+ requestInfo.isFirstUsage)
}
}
}
+ fun toCreateScreenState(
+ createOptionSize: Int,
+ isOnPasskeyIntroStateAlready: Boolean,
+ requestDisplayInfo: RequestDisplayInfo,
+ defaultProvider: EnabledProviderInfo?,
+ remoteEntry: RemoteInfo?,
+ ): CreateScreenState {
+ return if (requestDisplayInfo.isFirstUsage && requestDisplayInfo
+ .type == TYPE_PUBLIC_KEY_CREDENTIAL && !isOnPasskeyIntroStateAlready) {
+ CreateScreenState.PASSKEY_INTRO
+ } else if (
+ (defaultProvider == null || defaultProvider.createOptions.isEmpty()
+ ) && createOptionSize > 1) {
+ CreateScreenState.PROVIDER_SELECTION
+ } else if (
+ ((defaultProvider == null || defaultProvider.createOptions.isEmpty()
+ ) && createOptionSize == 1) || (
+ defaultProvider != null && defaultProvider.createOptions.isNotEmpty())) {
+ CreateScreenState.CREATION_OPTION_SELECTION
+ } else if (createOptionSize == 0 && remoteEntry != null) {
+ CreateScreenState.EXTERNAL_ONLY_SELECTION
+ } else {
+ // TODO: properly handle error and gracefully finish itself
+ throw java.lang.IllegalStateException("Empty provider list.")
+ }
+ }
+
+ fun toActiveEntry(
+ defaultProvider: EnabledProviderInfo?,
+ createOptionSize: Int,
+ lastSeenProviderWithNonEmptyCreateOptions: EnabledProviderInfo?,
+ remoteEntry: RemoteInfo?,
+ ): ActiveEntry? {
+ return if (
+ defaultProvider != null && defaultProvider.createOptions.isEmpty() && remoteEntry != null) {
+ ActiveEntry(defaultProvider, remoteEntry)
+ } else if (
+ defaultProvider != null && defaultProvider.createOptions.isNotEmpty()
+ ) {
+ ActiveEntry(defaultProvider, defaultProvider.createOptions.first())
+ } else if (createOptionSize == 1) {
+ ActiveEntry(lastSeenProviderWithNonEmptyCreateOptions!!,
+ lastSeenProviderWithNonEmptyCreateOptions.createOptions.first())
+ } else null
+ }
+
private fun toCreationOptionInfoList(
providerId: String,
creationEntries: List<Entry>,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/material/ModalBottomSheet.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/material/ModalBottomSheet.kt
index 61e11feff3c0..f1f453da4f38 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/material/ModalBottomSheet.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/material/ModalBottomSheet.kt
@@ -62,7 +62,6 @@ import com.android.credentialmanager.R
import com.android.credentialmanager.common.material.ModalBottomSheetValue.Expanded
import com.android.credentialmanager.common.material.ModalBottomSheetValue.HalfExpanded
import com.android.credentialmanager.common.material.ModalBottomSheetValue.Hidden
-import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.launch
import kotlin.math.max
@@ -319,7 +318,7 @@ fun ModalBottomSheetLayout(
rememberModalBottomSheetState(Hidden),
sheetShape: Shape = MaterialTheme.shapes.large,
sheetElevation: Dp = ModalBottomSheetDefaults.Elevation,
- sheetBackgroundColor: Color = ModalBottomSheetDefaults.scrimColor,
+ sheetBackgroundColor: Color = MaterialTheme.colorScheme.surface,
sheetContentColor: Color = contentColorFor(sheetBackgroundColor),
scrimColor: Color = ModalBottomSheetDefaults.scrimColor,
content: @Composable () -> Unit
@@ -477,5 +476,5 @@ object ModalBottomSheetDefaults {
*/
val scrimColor: Color
@Composable
- get() = LocalAndroidColorScheme.current.colorSurfaceHighlight
+ get() = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.32f)
} \ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/CancelButton.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/CancelButton.kt
index 177d0e0288c7..80764b5ec5bd 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/CancelButton.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/CancelButton.kt
@@ -16,13 +16,20 @@
package com.android.credentialmanager.common.ui
+import androidx.compose.material3.ButtonDefaults
+import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
@Composable
fun CancelButton(text: String, onClick: () -> Unit) {
- TextButton(onClick = onClick) {
+ TextButton(
+ onClick = onClick,
+ colors = ButtonDefaults.textButtonColors(
+ contentColor = MaterialTheme.colorScheme.primary,
+ )
+ ) {
Text(text = text)
}
} \ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Cards.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Cards.kt
new file mode 100644
index 000000000000..aaabce36c003
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Cards.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.credentialmanager.common.ui
+
+import androidx.compose.foundation.BorderStroke
+import androidx.compose.foundation.layout.ColumnScope
+import androidx.compose.material3.Card
+import androidx.compose.material3.CardDefaults
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Shape
+
+/**
+ * By default the card is filled with surfaceVariant color. This container card instead fills the
+ * background color with surface corlor.
+ */
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun ContainerCard(
+ modifier: Modifier = Modifier,
+ shape: Shape = CardDefaults.shape,
+ border: BorderStroke? = null,
+ content: @Composable ColumnScope.() -> Unit,
+) {
+ Card(
+ modifier = modifier,
+ shape = shape,
+ border = border,
+ colors = CardDefaults.cardColors(
+ containerColor = MaterialTheme.colorScheme.surface,
+ ),
+ content = content,
+ )
+} \ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/ConfirmButton.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/ConfirmButton.kt
index b2b0bdcdf3ce..d8ee750a88d6 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/ConfirmButton.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/ConfirmButton.kt
@@ -16,13 +16,21 @@
package com.android.credentialmanager.common.ui
+import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.FilledTonalButton
+import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@Composable
fun ConfirmButton(text: String, onClick: () -> Unit) {
- FilledTonalButton(onClick = onClick) {
+ FilledTonalButton(
+ onClick = onClick,
+ colors = ButtonDefaults.filledTonalButtonColors(
+ containerColor = MaterialTheme.colorScheme.primaryContainer,
+ contentColor = MaterialTheme.colorScheme.onPrimaryContainer,
+ )
+ ) {
Text(text = text)
}
} \ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
index 51a1cbbbf942..aefd534da63c 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
@@ -18,13 +18,13 @@ package com.android.credentialmanager.common.ui
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.SuggestionChip
import androidx.compose.material3.SuggestionChipDefaults
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import com.android.credentialmanager.ui.theme.EntryShape
-import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@@ -42,7 +42,9 @@ fun Entry(
icon = icon,
border = null,
colors = SuggestionChipDefaults.suggestionChipColors(
- containerColor = LocalAndroidColorScheme.current.colorSurface,
+ containerColor = MaterialTheme.colorScheme.surfaceVariant,
+ labelColor = MaterialTheme.colorScheme.onSurfaceVariant,
+ iconContentColor = MaterialTheme.colorScheme.onSurfaceVariant,
),
)
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Texts.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Texts.kt
new file mode 100644
index 000000000000..3a66dda73832
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Texts.kt
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.credentialmanager.common.ui
+
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.style.TextAlign
+
+@Composable
+fun TextOnSurface(
+ text: String,
+ modifier: Modifier = Modifier,
+ textAlign: TextAlign? = null,
+ style: TextStyle,
+) {
+ TextInternal(
+ text = text,
+ color = MaterialTheme.colorScheme.onSurface,
+ modifier = modifier,
+ textAlign = textAlign,
+ style = style,
+ )
+}
+
+@Composable
+fun TextSecondary(
+ text: String,
+ modifier: Modifier = Modifier,
+ textAlign: TextAlign? = null,
+ style: TextStyle,
+) {
+ TextInternal(
+ text = text,
+ color = MaterialTheme.colorScheme.secondary,
+ modifier = modifier,
+ textAlign = textAlign,
+ style = style,
+ )
+}
+
+@Composable
+fun TextOnSurfaceVariant(
+ text: String,
+ modifier: Modifier = Modifier,
+ textAlign: TextAlign? = null,
+ style: TextStyle,
+) {
+ TextInternal(
+ text = text,
+ color = MaterialTheme.colorScheme.onSurfaceVariant,
+ modifier = modifier,
+ textAlign = textAlign,
+ style = style,
+ )
+}
+
+@Composable
+private fun TextInternal(
+ text: String,
+ color: Color,
+ modifier: Modifier,
+ textAlign: TextAlign?,
+ style: TextStyle,
+) {
+ Text(text = text, color = color, modifier = modifier, textAlign = textAlign, style = style)
+} \ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
index 5552d0509355..b417b385ada7 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
@@ -14,7 +14,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
-import androidx.compose.material3.Card
+import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Divider
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
@@ -46,6 +46,10 @@ import com.android.credentialmanager.common.material.rememberModalBottomSheetSta
import com.android.credentialmanager.common.ui.CancelButton
import com.android.credentialmanager.common.ui.ConfirmButton
import com.android.credentialmanager.common.ui.Entry
+import com.android.credentialmanager.common.ui.TextOnSurface
+import com.android.credentialmanager.common.ui.TextSecondary
+import com.android.credentialmanager.common.ui.TextOnSurfaceVariant
+import com.android.credentialmanager.common.ui.ContainerCard
import com.android.credentialmanager.ui.theme.EntryShape
import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme
import com.android.credentialmanager.jetpack.developer.PublicKeyCredential.Companion.TYPE_PUBLIC_KEY_CREDENTIAL
@@ -53,715 +57,815 @@ import com.android.credentialmanager.jetpack.developer.PublicKeyCredential.Compa
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun CreateCredentialScreen(
- viewModel: CreateCredentialViewModel,
- providerActivityLauncher: ManagedActivityResultLauncher<IntentSenderRequest, ActivityResult>
+ viewModel: CreateCredentialViewModel,
+ providerActivityLauncher: ManagedActivityResultLauncher<IntentSenderRequest, ActivityResult>
) {
- val selectEntryCallback: (EntryInfo) -> Unit = {
- viewModel.onEntrySelected(it, providerActivityLauncher)
- }
- val confirmEntryCallback: () -> Unit = {
- viewModel.onConfirmEntrySelected(providerActivityLauncher)
- }
- val state = rememberModalBottomSheetState(
- initialValue = ModalBottomSheetValue.Expanded,
- skipHalfExpanded = true
- )
- ModalBottomSheetLayout(
- sheetState = state,
- sheetContent = {
- val uiState = viewModel.uiState
- when (uiState.currentScreenState) {
- CreateScreenState.PASSKEY_INTRO -> ConfirmationCard(
- onConfirm = viewModel::onConfirmIntro,
- onCancel = viewModel::onCancel,
- )
- CreateScreenState.PROVIDER_SELECTION -> ProviderSelectionCard(
- requestDisplayInfo = uiState.requestDisplayInfo,
- enabledProviderList = uiState.enabledProviders,
- disabledProviderList = uiState.disabledProviders,
- onCancel = viewModel::onCancel,
- onOptionSelected = viewModel::onEntrySelectedFromFirstUseScreen,
- onDisabledPasswordManagerSelected = viewModel::onDisabledPasswordManagerSelected,
- onRemoteEntrySelected = selectEntryCallback,
- )
- CreateScreenState.CREATION_OPTION_SELECTION -> CreationSelectionCard(
- requestDisplayInfo = uiState.requestDisplayInfo,
- enabledProviderList = uiState.enabledProviders,
- providerInfo = uiState.activeEntry?.activeProvider!!,
- createOptionInfo = uiState.activeEntry.activeEntryInfo as CreateOptionInfo,
- showActiveEntryOnly = uiState.showActiveEntryOnly,
- onOptionSelected = selectEntryCallback,
- onConfirm = confirmEntryCallback,
- onCancel = viewModel::onCancel,
- onMoreOptionsSelected = viewModel::onMoreOptionsSelected,
- )
- CreateScreenState.MORE_OPTIONS_SELECTION -> MoreOptionsSelectionCard(
- requestDisplayInfo = uiState.requestDisplayInfo,
- enabledProviderList = uiState.enabledProviders,
- disabledProviderList = uiState.disabledProviders,
- onBackButtonSelected = viewModel::onBackButtonSelected,
- onOptionSelected = viewModel::onEntrySelectedFromMoreOptionScreen,
- onDisabledPasswordManagerSelected = viewModel::onDisabledPasswordManagerSelected,
- onRemoteEntrySelected = selectEntryCallback,
- )
- CreateScreenState.MORE_OPTIONS_ROW_INTRO -> MoreOptionsRowIntroCard(
- providerInfo = uiState.activeEntry?.activeProvider!!,
- onDefaultOrNotSelected = viewModel::onDefaultOrNotSelected
- )
- CreateScreenState.EXTERNAL_ONLY_SELECTION -> ExternalOnlySelectionCard(
- requestDisplayInfo = uiState.requestDisplayInfo,
- activeRemoteEntry = uiState.activeEntry?.activeEntryInfo!!,
- onOptionSelected = selectEntryCallback,
- onConfirm = confirmEntryCallback,
- onCancel = viewModel::onCancel,
- )
- }
- },
- scrimColor = MaterialTheme.colorScheme.scrim.copy(alpha = 0.8f),
- sheetShape = EntryShape.TopRoundedCorner,
- ) {}
- LaunchedEffect(state.currentValue) {
- if (state.currentValue == ModalBottomSheetValue.Hidden) {
- viewModel.onCancel()
+ val state = rememberModalBottomSheetState(
+ initialValue = ModalBottomSheetValue.Expanded,
+ skipHalfExpanded = true
+ )
+ ModalBottomSheetLayout(
+ sheetBackgroundColor = MaterialTheme.colorScheme.surface,
+ sheetState = state,
+ sheetContent = {
+ val uiState = viewModel.uiState
+ if (!uiState.hidden) {
+ when (uiState.currentScreenState) {
+ CreateScreenState.PASSKEY_INTRO -> ConfirmationCard(
+ onConfirm = viewModel::onConfirmIntro,
+ onCancel = viewModel::onCancel,
+ )
+ CreateScreenState.PROVIDER_SELECTION -> ProviderSelectionCard(
+ requestDisplayInfo = uiState.requestDisplayInfo,
+ enabledProviderList = uiState.enabledProviders,
+ disabledProviderList = uiState.disabledProviders,
+ onCancel = viewModel::onCancel,
+ onOptionSelected = viewModel::onEntrySelectedFromFirstUseScreen,
+ onDisabledPasswordManagerSelected =
+ viewModel::onDisabledPasswordManagerSelected,
+ onRemoteEntrySelected = viewModel::onEntrySelected,
+ )
+ CreateScreenState.CREATION_OPTION_SELECTION -> CreationSelectionCard(
+ requestDisplayInfo = uiState.requestDisplayInfo,
+ enabledProviderList = uiState.enabledProviders,
+ providerInfo = uiState.activeEntry?.activeProvider!!,
+ createOptionInfo = uiState.activeEntry.activeEntryInfo as CreateOptionInfo,
+ showActiveEntryOnly = uiState.showActiveEntryOnly,
+ onOptionSelected = viewModel::onEntrySelected,
+ onConfirm = viewModel::onConfirmEntrySelected,
+ onCancel = viewModel::onCancel,
+ onMoreOptionsSelected = viewModel::onMoreOptionsSelected,
+ )
+ CreateScreenState.MORE_OPTIONS_SELECTION -> MoreOptionsSelectionCard(
+ requestDisplayInfo = uiState.requestDisplayInfo,
+ enabledProviderList = uiState.enabledProviders,
+ disabledProviderList = uiState.disabledProviders,
+ onBackButtonSelected = viewModel::onBackButtonSelected,
+ onOptionSelected = viewModel::onEntrySelectedFromMoreOptionScreen,
+ onDisabledPasswordManagerSelected =
+ viewModel::onDisabledPasswordManagerSelected,
+ onRemoteEntrySelected = viewModel::onEntrySelected,
+ )
+ CreateScreenState.MORE_OPTIONS_ROW_INTRO -> MoreOptionsRowIntroCard(
+ providerInfo = uiState.activeEntry?.activeProvider!!,
+ onDefaultOrNotSelected = viewModel::onDefaultOrNotSelected
+ )
+ CreateScreenState.EXTERNAL_ONLY_SELECTION -> ExternalOnlySelectionCard(
+ requestDisplayInfo = uiState.requestDisplayInfo,
+ activeRemoteEntry = uiState.activeEntry?.activeEntryInfo!!,
+ onOptionSelected = viewModel::onEntrySelected,
+ onConfirm = viewModel::onConfirmEntrySelected,
+ onCancel = viewModel::onCancel,
+ )
+ }
+ } else if (uiState.selectedEntry != null && !uiState.providerActivityPending) {
+ viewModel.launchProviderUi(providerActivityLauncher)
+ }
+ },
+ scrimColor = MaterialTheme.colorScheme.scrim.copy(alpha = 0.8f),
+ sheetShape = EntryShape.TopRoundedCorner,
+ ) {}
+ LaunchedEffect(state.currentValue) {
+ if (state.currentValue == ModalBottomSheetValue.Hidden) {
+ viewModel.onCancel()
+ }
}
- }
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ConfirmationCard(
- onConfirm: () -> Unit,
- onCancel: () -> Unit,
+ onConfirm: () -> Unit,
+ onCancel: () -> Unit,
) {
- Card() {
- Column() {
- Icon(
- painter = painterResource(R.drawable.ic_passkey),
- contentDescription = null,
- tint = LocalAndroidColorScheme.current.colorAccentPrimaryVariant,
- modifier = Modifier.align(alignment = Alignment.CenterHorizontally)
- .padding(top = 24.dp, bottom = 12.dp)
- )
- Text(
- text = stringResource(R.string.passkey_creation_intro_title),
- style = MaterialTheme.typography.titleMedium,
- modifier = Modifier
- .padding(horizontal = 24.dp)
- .align(alignment = Alignment.CenterHorizontally),
- textAlign = TextAlign.Center
- )
- Divider(
- thickness = 16.dp,
- color = Color.Transparent
- )
- Text(
- text = stringResource(R.string.passkey_creation_intro_body),
- style = MaterialTheme.typography.bodyLarge,
- modifier = Modifier.padding(horizontal = 28.dp)
- )
- Divider(
- thickness = 32.dp,
- color = Color.Transparent
- )
- Row(
- horizontalArrangement = Arrangement.SpaceBetween,
- modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
- ) {
- CancelButton(
- stringResource(R.string.string_cancel),
- onClick = onCancel
- )
- ConfirmButton(
- stringResource(R.string.string_continue),
- onClick = onConfirm
- )
- }
- Divider(
- thickness = 18.dp,
- color = Color.Transparent,
- modifier = Modifier.padding(bottom = 18.dp)
- )
+ ContainerCard() {
+ Column() {
+ Image(
+ painter = painterResource(R.drawable.ic_passkeys_onboarding),
+ contentDescription = null,
+ modifier = Modifier.align(alignment = Alignment.CenterHorizontally)
+ .padding(top = 24.dp, bottom = 12.dp).size(316.dp, 168.dp)
+ )
+ TextOnSurface(
+ text = stringResource(R.string.passkey_creation_intro_title),
+ style = MaterialTheme.typography.titleMedium,
+ modifier = Modifier
+ .padding(horizontal = 24.dp)
+ .align(alignment = Alignment.CenterHorizontally),
+ textAlign = TextAlign.Center,
+ )
+ Divider(
+ thickness = 16.dp,
+ color = Color.Transparent
+ )
+ Row(
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
+ ) {
+ Image(
+ modifier = Modifier.size(24.dp),
+ painter = painterResource(R.drawable.ic_passkeys_onboarding_password),
+ contentDescription = null
+ )
+ TextSecondary(
+ text = stringResource(R.string.passkey_creation_intro_body_password),
+ style = MaterialTheme.typography.bodyMedium,
+ modifier = Modifier.padding(start = 16.dp),
+ )
+ }
+ Divider(
+ thickness = 16.dp,
+ color = Color.Transparent
+ )
+ Row(
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
+ ) {
+ Image(
+ modifier = Modifier.size(24.dp),
+ painter = painterResource(R.drawable.ic_passkeys_onboarding_fingerprint),
+ contentDescription = null
+ )
+ TextSecondary(
+ text = stringResource(R.string.passkey_creation_intro_body_fingerprint),
+ style = MaterialTheme.typography.bodyMedium,
+ modifier = Modifier.padding(start = 16.dp),
+ )
+ }
+ Divider(
+ thickness = 16.dp,
+ color = Color.Transparent
+ )
+ Row(
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
+ ) {
+ Image(
+ modifier = Modifier.size(24.dp),
+ painter = painterResource(R.drawable.ic_passkeys_onboarding_device),
+ contentDescription = null
+ )
+ TextSecondary(
+ text = stringResource(R.string.passkey_creation_intro_body_device),
+ style = MaterialTheme.typography.bodyMedium,
+ modifier = Modifier.padding(start = 16.dp),
+ )
+ }
+ Divider(
+ thickness = 32.dp,
+ color = Color.Transparent
+ )
+ Row(
+ horizontalArrangement = Arrangement.SpaceBetween,
+ modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
+ ) {
+ CancelButton(
+ stringResource(R.string.string_cancel),
+ onClick = onCancel
+ )
+ ConfirmButton(
+ stringResource(R.string.string_continue),
+ onClick = onConfirm
+ )
+ }
+ Divider(
+ thickness = 18.dp,
+ color = Color.Transparent,
+ modifier = Modifier.padding(bottom = 18.dp)
+ )
+ }
}
- }
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ProviderSelectionCard(
- requestDisplayInfo: RequestDisplayInfo,
- enabledProviderList: List<EnabledProviderInfo>,
- disabledProviderList: List<DisabledProviderInfo>?,
- onOptionSelected: (ActiveEntry) -> Unit,
- onDisabledPasswordManagerSelected: () -> Unit,
- onCancel: () -> Unit,
- onRemoteEntrySelected: (EntryInfo) -> Unit,
+ requestDisplayInfo: RequestDisplayInfo,
+ enabledProviderList: List<EnabledProviderInfo>,
+ disabledProviderList: List<DisabledProviderInfo>?,
+ onOptionSelected: (ActiveEntry) -> Unit,
+ onDisabledPasswordManagerSelected: () -> Unit,
+ onCancel: () -> Unit,
+ onRemoteEntrySelected: (EntryInfo) -> Unit,
) {
- Card() {
- Column() {
- Icon(
- bitmap = requestDisplayInfo.typeIcon.toBitmap().asImageBitmap(),
- contentDescription = null,
- tint = LocalAndroidColorScheme.current.colorAccentPrimaryVariant,
- modifier = Modifier.align(alignment = Alignment.CenterHorizontally)
- .padding(top = 24.dp, bottom = 16.dp).size(32.dp)
- )
- Text(
- text = stringResource(
- R.string.choose_provider_title,
- when (requestDisplayInfo.type) {
- TYPE_PUBLIC_KEY_CREDENTIAL -> stringResource(R.string.create_your_passkeys)
- TYPE_PASSWORD_CREDENTIAL -> stringResource(R.string.save_your_password)
- else -> stringResource(R.string.save_your_sign_in_info)
- },
- ),
- style = MaterialTheme.typography.titleMedium,
- modifier = Modifier.padding(horizontal = 24.dp)
- .align(alignment = Alignment.CenterHorizontally),
- textAlign = TextAlign.Center
- )
- Divider(
- thickness = 16.dp,
- color = Color.Transparent
- )
- Text(
- text = stringResource(R.string.choose_provider_body),
- style = MaterialTheme.typography.bodyLarge,
- modifier = Modifier.padding(horizontal = 28.dp)
- )
- Divider(
- thickness = 18.dp,
- color = Color.Transparent
- )
- Card(
- shape = MaterialTheme.shapes.medium,
- modifier = Modifier
- .padding(horizontal = 24.dp)
- .align(alignment = Alignment.CenterHorizontally),
- ) {
- LazyColumn(
- verticalArrangement = Arrangement.spacedBy(2.dp)
- ) {
- enabledProviderList.forEach { enabledProviderInfo ->
- enabledProviderInfo.createOptions.forEach { createOptionInfo ->
- item {
- MoreOptionsInfoRow(
- providerInfo = enabledProviderInfo,
- createOptionInfo = createOptionInfo,
- onOptionSelected = {
- onOptionSelected(ActiveEntry(enabledProviderInfo, createOptionInfo))
- })
- }
+ ContainerCard() {
+ Column() {
+ Icon(
+ bitmap = requestDisplayInfo.typeIcon.toBitmap().asImageBitmap(),
+ contentDescription = null,
+ tint = LocalAndroidColorScheme.current.colorAccentPrimaryVariant,
+ modifier = Modifier.align(alignment = Alignment.CenterHorizontally)
+ .padding(top = 24.dp, bottom = 16.dp).size(32.dp)
+ )
+ TextOnSurface(
+ text = stringResource(
+ R.string.choose_provider_title,
+ when (requestDisplayInfo.type) {
+ TYPE_PUBLIC_KEY_CREDENTIAL -> stringResource(R.string.create_your_passkeys)
+ TYPE_PASSWORD_CREDENTIAL -> stringResource(R.string.save_your_password)
+ else -> stringResource(R.string.save_your_sign_in_info)
+ },
+ ),
+ style = MaterialTheme.typography.titleMedium,
+ modifier = Modifier.padding(horizontal = 24.dp)
+ .align(alignment = Alignment.CenterHorizontally),
+ textAlign = TextAlign.Center,
+ )
+ Divider(
+ thickness = 16.dp,
+ color = Color.Transparent
+ )
+ TextSecondary(
+ text = stringResource(R.string.choose_provider_body),
+ style = MaterialTheme.typography.bodyLarge,
+ modifier = Modifier.padding(horizontal = 28.dp),
+ )
+ Divider(
+ thickness = 18.dp,
+ color = Color.Transparent
+ )
+ ContainerCard(
+ shape = MaterialTheme.shapes.medium,
+ modifier = Modifier
+ .padding(horizontal = 24.dp)
+ .align(alignment = Alignment.CenterHorizontally),
+ ) {
+ LazyColumn(
+ verticalArrangement = Arrangement.spacedBy(2.dp)
+ ) {
+ enabledProviderList.forEach { enabledProviderInfo ->
+ enabledProviderInfo.createOptions.forEach { createOptionInfo ->
+ item {
+ MoreOptionsInfoRow(
+ providerInfo = enabledProviderInfo,
+ createOptionInfo = createOptionInfo,
+ onOptionSelected = {
+ onOptionSelected(
+ ActiveEntry(
+ enabledProviderInfo,
+ createOptionInfo
+ )
+ )
+ })
+ }
+ }
+ }
+ if (disabledProviderList != null) {
+ item {
+ MoreOptionsDisabledProvidersRow(
+ disabledProviders = disabledProviderList,
+ onDisabledPasswordManagerSelected =
+ onDisabledPasswordManagerSelected,
+ )
+ }
+ }
+ }
}
- }
- if (disabledProviderList != null) {
- item {
- MoreOptionsDisabledProvidersRow(
- disabledProviders = disabledProviderList,
- onDisabledPasswordManagerSelected = onDisabledPasswordManagerSelected,
- )
+ // TODO: handle the error situation that if multiple remoteInfos exists
+ enabledProviderList.forEach { enabledProvider ->
+ if (enabledProvider.remoteEntry != null) {
+ TextButton(
+ onClick = {
+ onRemoteEntrySelected(enabledProvider.remoteEntry!!)
+ },
+ modifier = Modifier
+ .padding(horizontal = 24.dp)
+ .align(alignment = Alignment.CenterHorizontally),
+ colors = ButtonDefaults.textButtonColors(
+ contentColor = MaterialTheme.colorScheme.primary,
+ )
+ ) {
+ Text(
+ text = stringResource(R.string.string_save_to_another_device),
+ textAlign = TextAlign.Center,
+ )
+ }
+ }
}
- }
- }
- }
- // TODO: handle the error situation that if multiple remoteInfos exists
- enabledProviderList.forEach { enabledProvider ->
- if (enabledProvider.remoteEntry != null) {
- TextButton(
- onClick = {
- onRemoteEntrySelected(enabledProvider.remoteEntry!!) },
- modifier = Modifier
- .padding(horizontal = 24.dp)
- .align(alignment = Alignment.CenterHorizontally)
- ) {
- Text(
- text = stringResource(R.string.string_save_to_another_device),
- textAlign = TextAlign.Center,
- )
- }
+ Divider(
+ thickness = 24.dp,
+ color = Color.Transparent
+ )
+ Row(
+ horizontalArrangement = Arrangement.Start,
+ modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
+ ) {
+ CancelButton(stringResource(R.string.string_cancel), onCancel)
+ }
+ Divider(
+ thickness = 18.dp,
+ color = Color.Transparent,
+ modifier = Modifier.padding(bottom = 16.dp)
+ )
}
- }
- Divider(
- thickness = 24.dp,
- color = Color.Transparent
- )
- Row(
- horizontalArrangement = Arrangement.Start,
- modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
- ) {
- CancelButton(stringResource(R.string.string_cancel), onCancel)
- }
- Divider(
- thickness = 18.dp,
- color = Color.Transparent,
- modifier = Modifier.padding(bottom = 16.dp)
- )
}
- }
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MoreOptionsSelectionCard(
- requestDisplayInfo: RequestDisplayInfo,
- enabledProviderList: List<EnabledProviderInfo>,
- disabledProviderList: List<DisabledProviderInfo>?,
- onBackButtonSelected: () -> Unit,
- onOptionSelected: (ActiveEntry) -> Unit,
- onDisabledPasswordManagerSelected: () -> Unit,
- onRemoteEntrySelected: (EntryInfo) -> Unit,
+ requestDisplayInfo: RequestDisplayInfo,
+ enabledProviderList: List<EnabledProviderInfo>,
+ disabledProviderList: List<DisabledProviderInfo>?,
+ onBackButtonSelected: () -> Unit,
+ onOptionSelected: (ActiveEntry) -> Unit,
+ onDisabledPasswordManagerSelected: () -> Unit,
+ onRemoteEntrySelected: (EntryInfo) -> Unit,
) {
- Card() {
- Column() {
- TopAppBar(
- title = {
- Text(
- text = when (requestDisplayInfo.type) {
- TYPE_PUBLIC_KEY_CREDENTIAL -> stringResource(R.string.create_passkey_in_title)
- TYPE_PASSWORD_CREDENTIAL -> stringResource(R.string.save_password_to_title)
- else -> stringResource(R.string.save_sign_in_to_title)
- },
- style = MaterialTheme.typography.titleMedium
- )
- },
- navigationIcon = {
- IconButton(onClick = onBackButtonSelected) {
- Icon(
- Icons.Filled.ArrowBack,
- stringResource(R.string.accessibility_back_arrow_button))
- }
- },
- colors = TopAppBarDefaults.smallTopAppBarColors
- (containerColor = Color.Transparent),
- )
- Divider(
- thickness = 8.dp,
- color = Color.Transparent
- )
- Card(
- shape = MaterialTheme.shapes.medium,
- modifier = Modifier
- .padding(horizontal = 24.dp)
- .align(alignment = Alignment.CenterHorizontally)
- ) {
- LazyColumn(
- verticalArrangement = Arrangement.spacedBy(2.dp)
- ) {
- enabledProviderList.forEach { enabledProviderInfo ->
- enabledProviderInfo.createOptions.forEach { createOptionInfo ->
- item {
- MoreOptionsInfoRow(
- providerInfo = enabledProviderInfo,
- createOptionInfo = createOptionInfo,
- onOptionSelected = {
- onOptionSelected(ActiveEntry(enabledProviderInfo, createOptionInfo))
- })
- }
- }
- }
- if (disabledProviderList != null) {
- item {
- MoreOptionsDisabledProvidersRow(
- disabledProviders = disabledProviderList,
- onDisabledPasswordManagerSelected = onDisabledPasswordManagerSelected,
- )
- }
- }
- // TODO: handle the error situation that if multiple remoteInfos exists
- enabledProviderList.forEach {
- if (it.remoteEntry != null) {
- item {
- RemoteEntryRow(
- remoteInfo = it.remoteEntry!!,
- onRemoteEntrySelected = onRemoteEntrySelected,
- )
- }
+ ContainerCard() {
+ Column() {
+ TopAppBar(
+ title = {
+ TextOnSurface(
+ text = when (requestDisplayInfo.type) {
+ TYPE_PUBLIC_KEY_CREDENTIAL ->
+ stringResource(R.string.create_passkey_in_title)
+ TYPE_PASSWORD_CREDENTIAL ->
+ stringResource(R.string.save_password_to_title)
+ else -> stringResource(R.string.save_sign_in_to_title)
+ },
+ style = MaterialTheme.typography.titleMedium,
+ )
+ },
+ navigationIcon = {
+ IconButton(onClick = onBackButtonSelected) {
+ Icon(
+ Icons.Filled.ArrowBack,
+ stringResource(R.string.accessibility_back_arrow_button)
+ )
+ }
+ },
+ colors = TopAppBarDefaults.smallTopAppBarColors
+ (containerColor = Color.Transparent),
+ )
+ Divider(
+ thickness = 8.dp,
+ color = Color.Transparent
+ )
+ ContainerCard(
+ shape = MaterialTheme.shapes.medium,
+ modifier = Modifier
+ .padding(horizontal = 24.dp)
+ .align(alignment = Alignment.CenterHorizontally)
+ ) {
+ LazyColumn(
+ verticalArrangement = Arrangement.spacedBy(2.dp)
+ ) {
+ enabledProviderList.forEach { enabledProviderInfo ->
+ enabledProviderInfo.createOptions.forEach { createOptionInfo ->
+ item {
+ MoreOptionsInfoRow(
+ providerInfo = enabledProviderInfo,
+ createOptionInfo = createOptionInfo,
+ onOptionSelected = {
+ onOptionSelected(
+ ActiveEntry(
+ enabledProviderInfo,
+ createOptionInfo
+ )
+ )
+ })
+ }
+ }
+ }
+ if (disabledProviderList != null) {
+ item {
+ MoreOptionsDisabledProvidersRow(
+ disabledProviders = disabledProviderList,
+ onDisabledPasswordManagerSelected =
+ onDisabledPasswordManagerSelected,
+ )
+ }
+ }
+ // TODO: handle the error situation that if multiple remoteInfos exists
+ enabledProviderList.forEach {
+ if (it.remoteEntry != null) {
+ item {
+ RemoteEntryRow(
+ remoteInfo = it.remoteEntry!!,
+ onRemoteEntrySelected = onRemoteEntrySelected,
+ )
+ }
+ }
+ }
+ }
}
- }
+ Divider(
+ thickness = 18.dp,
+ color = Color.Transparent,
+ modifier = Modifier.padding(bottom = 40.dp)
+ )
}
- }
- Divider(
- thickness = 18.dp,
- color = Color.Transparent,
- modifier = Modifier.padding(bottom = 40.dp)
- )
}
- }
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MoreOptionsRowIntroCard(
- providerInfo: EnabledProviderInfo,
- onDefaultOrNotSelected: () -> Unit,
+ providerInfo: EnabledProviderInfo,
+ onDefaultOrNotSelected: () -> Unit,
) {
- Card() {
- Column() {
- Icon(
- Icons.Outlined.NewReleases,
- contentDescription = null,
- modifier = Modifier.align(alignment = Alignment.CenterHorizontally).padding(all = 24.dp)
- )
- Text(
- text = stringResource(R.string.use_provider_for_all_title, providerInfo.displayName),
- style = MaterialTheme.typography.titleMedium,
- modifier = Modifier.padding(horizontal = 24.dp)
- .align(alignment = Alignment.CenterHorizontally),
- textAlign = TextAlign.Center,
- )
- Text(
- text = stringResource(R.string.use_provider_for_all_description),
- style = MaterialTheme.typography.bodyLarge,
- modifier = Modifier.padding(all = 24.dp).align(alignment = Alignment.CenterHorizontally)
- )
- Row(
- horizontalArrangement = Arrangement.SpaceBetween,
- modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
- ) {
- CancelButton(
- stringResource(R.string.use_once),
- onClick = onDefaultOrNotSelected
- )
- ConfirmButton(
- stringResource(R.string.set_as_default),
- onClick = onDefaultOrNotSelected
- )
- }
- Divider(
- thickness = 18.dp,
- color = Color.Transparent,
- modifier = Modifier.padding(bottom = 40.dp)
- )
+ ContainerCard() {
+ Column() {
+ Icon(
+ Icons.Outlined.NewReleases,
+ contentDescription = null,
+ modifier = Modifier.align(alignment = Alignment.CenterHorizontally)
+ .padding(all = 24.dp)
+ )
+ TextOnSurface(
+ text = stringResource(
+ R.string.use_provider_for_all_title,
+ providerInfo.displayName
+ ),
+ style = MaterialTheme.typography.titleMedium,
+ modifier = Modifier.padding(horizontal = 24.dp)
+ .align(alignment = Alignment.CenterHorizontally),
+ textAlign = TextAlign.Center,
+ )
+ TextSecondary(
+ text = stringResource(R.string.use_provider_for_all_description),
+ style = MaterialTheme.typography.bodyLarge,
+ modifier = Modifier.padding(all = 24.dp)
+ .align(alignment = Alignment.CenterHorizontally),
+ )
+ Row(
+ horizontalArrangement = Arrangement.SpaceBetween,
+ modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
+ ) {
+ CancelButton(
+ stringResource(R.string.use_once),
+ onClick = onDefaultOrNotSelected
+ )
+ ConfirmButton(
+ stringResource(R.string.set_as_default),
+ onClick = onDefaultOrNotSelected
+ )
+ }
+ Divider(
+ thickness = 18.dp,
+ color = Color.Transparent,
+ modifier = Modifier.padding(bottom = 40.dp)
+ )
+ }
}
- }
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun CreationSelectionCard(
- requestDisplayInfo: RequestDisplayInfo,
- enabledProviderList: List<EnabledProviderInfo>,
- providerInfo: EnabledProviderInfo,
- createOptionInfo: CreateOptionInfo,
- showActiveEntryOnly: Boolean,
- onOptionSelected: (EntryInfo) -> Unit,
- onConfirm: () -> Unit,
- onCancel: () -> Unit,
- onMoreOptionsSelected: () -> Unit,
+ requestDisplayInfo: RequestDisplayInfo,
+ enabledProviderList: List<EnabledProviderInfo>,
+ providerInfo: EnabledProviderInfo,
+ createOptionInfo: CreateOptionInfo,
+ showActiveEntryOnly: Boolean,
+ onOptionSelected: (EntryInfo) -> Unit,
+ onConfirm: () -> Unit,
+ onCancel: () -> Unit,
+ onMoreOptionsSelected: () -> Unit,
) {
- Card() {
- Column() {
- Icon(
- bitmap = providerInfo.icon.toBitmap().asImageBitmap(),
- contentDescription = null,
- tint = Color.Unspecified,
- modifier = Modifier.align(alignment = Alignment.CenterHorizontally)
- .padding(all = 24.dp).size(32.dp)
- )
- Text(
- text = when (requestDisplayInfo.type) {
- TYPE_PUBLIC_KEY_CREDENTIAL -> stringResource(R.string.choose_create_option_passkey_title,
- providerInfo.displayName)
- TYPE_PASSWORD_CREDENTIAL -> stringResource(R.string.choose_create_option_password_title,
- providerInfo.displayName)
- else -> stringResource(R.string.choose_create_option_sign_in_title,
- providerInfo.displayName)
- },
- style = MaterialTheme.typography.titleMedium,
- modifier = Modifier.padding(horizontal = 24.dp)
- .align(alignment = Alignment.CenterHorizontally),
- textAlign = TextAlign.Center,
- )
- if (createOptionInfo.userProviderDisplayName != null) {
- Text(
- text = stringResource(
- R.string.choose_create_option_description,
- requestDisplayInfo.appDomainName,
- when (requestDisplayInfo.type) {
- TYPE_PUBLIC_KEY_CREDENTIAL -> stringResource(R.string.passkey)
- TYPE_PASSWORD_CREDENTIAL -> stringResource(R.string.password)
- else -> stringResource(R.string.sign_ins)
- },
- providerInfo.displayName,
- createOptionInfo.userProviderDisplayName
- ),
- style = MaterialTheme.typography.bodyLarge,
- modifier = Modifier.padding(all = 24.dp).align(alignment = Alignment.CenterHorizontally)
- )
- }
- Card(
- shape = MaterialTheme.shapes.medium,
- modifier = Modifier
- .padding(horizontal = 24.dp)
- .align(alignment = Alignment.CenterHorizontally),
- ) {
- PrimaryCreateOptionRow(
- requestDisplayInfo = requestDisplayInfo,
- entryInfo = createOptionInfo,
- onOptionSelected = onOptionSelected
- )
- }
- if (!showActiveEntryOnly) {
- var createOptionsSize = 0
- enabledProviderList.forEach{
- enabledProvider -> createOptionsSize += enabledProvider.createOptions.size}
- if (createOptionsSize > 1) {
- TextButton(
- onClick = onMoreOptionsSelected,
- modifier = Modifier
- .padding(horizontal = 24.dp)
- .align(alignment = Alignment.CenterHorizontally)){
- Text(
- text =
- when (requestDisplayInfo.type) {
- TYPE_PUBLIC_KEY_CREDENTIAL ->
- stringResource(R.string.string_create_in_another_place)
- else -> stringResource(R.string.string_save_to_another_place)},
- textAlign = TextAlign.Center,
- )
- }
- } else if (
- requestDisplayInfo.type == TYPE_PUBLIC_KEY_CREDENTIAL
- ) {
- // TODO: handle the error situation that if multiple remoteInfos exists
- enabledProviderList.forEach { enabledProvider ->
- if (enabledProvider.remoteEntry != null) {
- TextButton(
- onClick = {
- onOptionSelected(enabledProvider.remoteEntry!!) },
+ ContainerCard() {
+ Column() {
+ Icon(
+ bitmap = providerInfo.icon.toBitmap().asImageBitmap(),
+ contentDescription = null,
+ tint = Color.Unspecified,
+ modifier = Modifier.align(alignment = Alignment.CenterHorizontally)
+ .padding(all = 24.dp).size(32.dp)
+ )
+ TextOnSurface(
+ text = when (requestDisplayInfo.type) {
+ TYPE_PUBLIC_KEY_CREDENTIAL -> stringResource(
+ R.string.choose_create_option_passkey_title,
+ providerInfo.displayName
+ )
+ TYPE_PASSWORD_CREDENTIAL -> stringResource(
+ R.string.choose_create_option_password_title,
+ providerInfo.displayName
+ )
+ else -> stringResource(
+ R.string.choose_create_option_sign_in_title,
+ providerInfo.displayName
+ )
+ },
+ style = MaterialTheme.typography.titleMedium,
+ modifier = Modifier.padding(horizontal = 24.dp)
+ .align(alignment = Alignment.CenterHorizontally),
+ textAlign = TextAlign.Center,
+ )
+ if (createOptionInfo.userProviderDisplayName != null) {
+ TextSecondary(
+ text = stringResource(
+ R.string.choose_create_option_description,
+ requestDisplayInfo.appDomainName,
+ when (requestDisplayInfo.type) {
+ TYPE_PUBLIC_KEY_CREDENTIAL -> stringResource(R.string.passkey)
+ TYPE_PASSWORD_CREDENTIAL -> stringResource(R.string.password)
+ else -> stringResource(R.string.sign_ins)
+ },
+ providerInfo.displayName,
+ createOptionInfo.userProviderDisplayName
+ ),
+ style = MaterialTheme.typography.bodyLarge,
+ modifier = Modifier.padding(all = 24.dp)
+ .align(alignment = Alignment.CenterHorizontally),
+ )
+ }
+ ContainerCard(
+ shape = MaterialTheme.shapes.medium,
modifier = Modifier
- .padding(horizontal = 24.dp)
- .align(alignment = Alignment.CenterHorizontally)
- ) {
- Text(
- text = stringResource(R.string.string_use_another_device),
- textAlign = TextAlign.Center,
+ .padding(horizontal = 24.dp)
+ .align(alignment = Alignment.CenterHorizontally),
+ ) {
+ PrimaryCreateOptionRow(
+ requestDisplayInfo = requestDisplayInfo,
+ entryInfo = createOptionInfo,
+ onOptionSelected = onOptionSelected
+ )
+ }
+ if (!showActiveEntryOnly) {
+ var createOptionsSize = 0
+ enabledProviderList.forEach { enabledProvider ->
+ createOptionsSize += enabledProvider.createOptions.size
+ }
+ if (createOptionsSize > 1) {
+ TextButton(
+ onClick = onMoreOptionsSelected,
+ modifier = Modifier
+ .padding(horizontal = 24.dp)
+ .align(alignment = Alignment.CenterHorizontally),
+ colors = ButtonDefaults.textButtonColors(
+ contentColor = MaterialTheme.colorScheme.primary,
+ ),
+ ) {
+ Text(
+ text =
+ when (requestDisplayInfo.type) {
+ TYPE_PUBLIC_KEY_CREDENTIAL ->
+ stringResource(R.string.string_create_in_another_place)
+ else -> stringResource(R.string.string_save_to_another_place)
+ },
+ textAlign = TextAlign.Center,
+ )
+ }
+ } else if (
+ requestDisplayInfo.type == TYPE_PUBLIC_KEY_CREDENTIAL
+ ) {
+ // TODO: handle the error situation that if multiple remoteInfos exists
+ enabledProviderList.forEach { enabledProvider ->
+ if (enabledProvider.remoteEntry != null) {
+ TextButton(
+ onClick = {
+ onOptionSelected(enabledProvider.remoteEntry!!)
+ },
+ modifier = Modifier
+ .padding(horizontal = 24.dp)
+ .align(alignment = Alignment.CenterHorizontally),
+ colors = ButtonDefaults.textButtonColors(
+ contentColor = MaterialTheme.colorScheme.primary,
+ ),
+ ) {
+ Text(
+ text = stringResource(R.string.string_use_another_device),
+ textAlign = TextAlign.Center,
+ )
+ }
+ }
+ }
+ }
+ }
+ Divider(
+ thickness = 24.dp,
+ color = Color.Transparent
+ )
+ Row(
+ horizontalArrangement = Arrangement.SpaceBetween,
+ modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
+ ) {
+ CancelButton(
+ stringResource(R.string.string_cancel),
+ onClick = onCancel
+ )
+ ConfirmButton(
+ stringResource(R.string.string_continue),
+ onClick = onConfirm
)
- }
}
- }
+ Divider(
+ thickness = 18.dp,
+ color = Color.Transparent,
+ modifier = Modifier.padding(bottom = 16.dp)
+ )
}
- }
- Divider(
- thickness = 24.dp,
- color = Color.Transparent
- )
- Row(
- horizontalArrangement = Arrangement.SpaceBetween,
- modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
- ) {
- CancelButton(
- stringResource(R.string.string_cancel),
- onClick = onCancel
- )
- ConfirmButton(
- stringResource(R.string.string_continue),
- onClick = onConfirm
- )
- }
- Divider(
- thickness = 18.dp,
- color = Color.Transparent,
- modifier = Modifier.padding(bottom = 16.dp)
- )
}
- }
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ExternalOnlySelectionCard(
- requestDisplayInfo: RequestDisplayInfo,
- activeRemoteEntry: EntryInfo,
- onOptionSelected: (EntryInfo) -> Unit,
- onConfirm: () -> Unit,
- onCancel: () -> Unit,
+ requestDisplayInfo: RequestDisplayInfo,
+ activeRemoteEntry: EntryInfo,
+ onOptionSelected: (EntryInfo) -> Unit,
+ onConfirm: () -> Unit,
+ onCancel: () -> Unit,
) {
- Card() {
- Column() {
- Icon(
- painter = painterResource(R.drawable.ic_other_devices),
- contentDescription = null,
- tint = Color.Unspecified,
- modifier = Modifier.align(alignment = Alignment.CenterHorizontally)
- .padding(all = 24.dp).size(32.dp)
- )
- Text(
- text = stringResource(R.string.create_passkey_in_other_device_title),
- style = MaterialTheme.typography.titleMedium,
- modifier = Modifier.padding(horizontal = 24.dp)
- .align(alignment = Alignment.CenterHorizontally),
- textAlign = TextAlign.Center,
- )
- Divider(
- thickness = 24.dp,
- color = Color.Transparent
- )
- Card(
- shape = MaterialTheme.shapes.medium,
- modifier = Modifier
- .padding(horizontal = 24.dp)
- .align(alignment = Alignment.CenterHorizontally),
- ) {
- PrimaryCreateOptionRow(
- requestDisplayInfo = requestDisplayInfo,
- entryInfo = activeRemoteEntry,
- onOptionSelected = onOptionSelected
- )
- }
- Divider(
- thickness = 24.dp,
- color = Color.Transparent
- )
- Row(
- horizontalArrangement = Arrangement.SpaceBetween,
- modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
- ) {
- CancelButton(
- stringResource(R.string.string_cancel),
- onClick = onCancel
- )
- ConfirmButton(
- stringResource(R.string.string_continue),
- onClick = onConfirm
- )
- }
- Divider(
- thickness = 18.dp,
- color = Color.Transparent,
- modifier = Modifier.padding(bottom = 16.dp)
- )
+ ContainerCard() {
+ Column() {
+ Icon(
+ painter = painterResource(R.drawable.ic_other_devices),
+ contentDescription = null,
+ tint = Color.Unspecified,
+ modifier = Modifier.align(alignment = Alignment.CenterHorizontally)
+ .padding(all = 24.dp).size(32.dp)
+ )
+ TextOnSurface(
+ text = stringResource(R.string.create_passkey_in_other_device_title),
+ style = MaterialTheme.typography.titleMedium,
+ modifier = Modifier.padding(horizontal = 24.dp)
+ .align(alignment = Alignment.CenterHorizontally),
+ textAlign = TextAlign.Center,
+ )
+ Divider(
+ thickness = 24.dp,
+ color = Color.Transparent
+ )
+ ContainerCard(
+ shape = MaterialTheme.shapes.medium,
+ modifier = Modifier
+ .padding(horizontal = 24.dp)
+ .align(alignment = Alignment.CenterHorizontally),
+ ) {
+ PrimaryCreateOptionRow(
+ requestDisplayInfo = requestDisplayInfo,
+ entryInfo = activeRemoteEntry,
+ onOptionSelected = onOptionSelected
+ )
+ }
+ Divider(
+ thickness = 24.dp,
+ color = Color.Transparent
+ )
+ Row(
+ horizontalArrangement = Arrangement.SpaceBetween,
+ modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
+ ) {
+ CancelButton(
+ stringResource(R.string.string_cancel),
+ onClick = onCancel
+ )
+ ConfirmButton(
+ stringResource(R.string.string_continue),
+ onClick = onConfirm
+ )
+ }
+ Divider(
+ thickness = 18.dp,
+ color = Color.Transparent,
+ modifier = Modifier.padding(bottom = 16.dp)
+ )
+ }
}
- }
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun PrimaryCreateOptionRow(
- requestDisplayInfo: RequestDisplayInfo,
- entryInfo: EntryInfo,
- onOptionSelected: (EntryInfo) -> Unit
+ requestDisplayInfo: RequestDisplayInfo,
+ entryInfo: EntryInfo,
+ onOptionSelected: (EntryInfo) -> Unit
) {
- Entry(
- onClick = {onOptionSelected(entryInfo)},
- icon = {
- Icon(
- bitmap = if (entryInfo is CreateOptionInfo) {
- entryInfo.profileIcon.toBitmap().asImageBitmap()
- } else {requestDisplayInfo.typeIcon.toBitmap().asImageBitmap()},
- contentDescription = null,
- tint = LocalAndroidColorScheme.current.colorAccentPrimaryVariant,
- modifier = Modifier.padding(start = 18.dp).size(32.dp)
- )
- },
- label = {
- Column() {
- // TODO: Add the function to hide/view password when the type is create password
- when (requestDisplayInfo.type) {
- TYPE_PUBLIC_KEY_CREDENTIAL -> {
- Text(
- text = requestDisplayInfo.title,
- style = MaterialTheme.typography.titleLarge,
- modifier = Modifier.padding(top = 16.dp)
- )
- Text(
- text = if (requestDisplayInfo.subtitle != null) {
- stringResource(
- R.string.passkey_before_subtitle) + " - " + requestDisplayInfo.subtitle
- } else {stringResource(R.string.passkey_before_subtitle)},
- style = MaterialTheme.typography.bodyMedium,
- modifier = Modifier.padding(bottom = 16.dp)
- )
- }
- TYPE_PASSWORD_CREDENTIAL -> {
- Text(
- text = requestDisplayInfo.title,
- style = MaterialTheme.typography.titleLarge,
- modifier = Modifier.padding(top = 16.dp)
- )
- Text(
- // This subtitle would never be null for create password
- text = requestDisplayInfo.subtitle ?: "",
- style = MaterialTheme.typography.bodyMedium,
- modifier = Modifier.padding(bottom = 16.dp)
- )
- }
- else -> {
- Text(
- text = requestDisplayInfo.title,
- style = MaterialTheme.typography.titleLarge,
- modifier = Modifier.padding(top = 16.dp, bottom = 16.dp)
- )
+ Entry(
+ onClick = { onOptionSelected(entryInfo) },
+ icon = {
+ Icon(
+ bitmap = if (entryInfo is CreateOptionInfo) {
+ entryInfo.profileIcon.toBitmap().asImageBitmap()
+ } else {
+ requestDisplayInfo.typeIcon.toBitmap().asImageBitmap()
+ },
+ contentDescription = null,
+ tint = LocalAndroidColorScheme.current.colorAccentPrimaryVariant,
+ modifier = Modifier.padding(horizontal = 18.dp).size(32.dp)
+ )
+ },
+ label = {
+ Column() {
+ // TODO: Add the function to hide/view password when the type is create password
+ when (requestDisplayInfo.type) {
+ TYPE_PUBLIC_KEY_CREDENTIAL -> {
+ TextOnSurfaceVariant(
+ text = requestDisplayInfo.title,
+ style = MaterialTheme.typography.titleLarge,
+ modifier = Modifier.padding(top = 16.dp),
+ )
+ TextSecondary(
+ text = if (requestDisplayInfo.subtitle != null) {
+ stringResource(
+ R.string.passkey_before_subtitle
+ ) + " - " + requestDisplayInfo.subtitle
+ } else {
+ stringResource(R.string.passkey_before_subtitle)
+ },
+ style = MaterialTheme.typography.bodyMedium,
+ modifier = Modifier.padding(bottom = 16.dp),
+ )
+ }
+ TYPE_PASSWORD_CREDENTIAL -> {
+ TextOnSurfaceVariant(
+ text = requestDisplayInfo.title,
+ style = MaterialTheme.typography.titleLarge,
+ modifier = Modifier.padding(top = 16.dp),
+ )
+ TextSecondary(
+ // This subtitle would never be null for create password
+ text = requestDisplayInfo.subtitle ?: "",
+ style = MaterialTheme.typography.bodyMedium,
+ modifier = Modifier.padding(bottom = 16.dp),
+ )
+ }
+ else -> {
+ TextOnSurfaceVariant(
+ text = requestDisplayInfo.title,
+ style = MaterialTheme.typography.titleLarge,
+ modifier = Modifier.padding(top = 16.dp, bottom = 16.dp),
+ )
+ }
+ }
}
}
- }
- }
- )
+ )
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MoreOptionsInfoRow(
- providerInfo: EnabledProviderInfo,
- createOptionInfo: CreateOptionInfo,
- onOptionSelected: () -> Unit
+ providerInfo: EnabledProviderInfo,
+ createOptionInfo: CreateOptionInfo,
+ onOptionSelected: () -> Unit
) {
- Entry(
+ Entry(
onClick = onOptionSelected,
icon = {
- Image(modifier = Modifier.size(32.dp).padding(start = 16.dp),
+ Image(
+ modifier = Modifier.padding(horizontal = 16.dp).size(32.dp),
bitmap = providerInfo.icon.toBitmap().asImageBitmap(),
- contentDescription = null)
+ contentDescription = null
+ )
},
label = {
- Column() {
- Text(
- text = providerInfo.displayName,
- style = MaterialTheme.typography.titleLarge,
- modifier = Modifier.padding(top = 16.dp, start = 16.dp)
- )
- if (createOptionInfo.userProviderDisplayName != null) {
- Text(
- text = createOptionInfo.userProviderDisplayName,
- style = MaterialTheme.typography.bodyMedium,
- modifier = Modifier.padding(start = 16.dp)
- )
- }
- if (createOptionInfo.passwordCount != null && createOptionInfo.passkeyCount != null) {
- Text(
- text =
- stringResource(
- R.string.more_options_usage_passwords_passkeys,
- createOptionInfo.passwordCount,
- createOptionInfo.passkeyCount
- ),
- style = MaterialTheme.typography.bodyMedium,
- modifier = Modifier.padding(bottom = 16.dp, start = 16.dp)
- )
- } else if (createOptionInfo.passwordCount != null) {
- Text(
- text =
- stringResource(
- R.string.more_options_usage_passwords,
- createOptionInfo.passwordCount
- ),
- style = MaterialTheme.typography.bodyMedium,
- modifier = Modifier.padding(bottom = 16.dp, start = 16.dp)
- )
- } else if (createOptionInfo.passkeyCount != null) {
- Text(
- text =
- stringResource(
- R.string.more_options_usage_passkeys,
- createOptionInfo.passkeyCount
- ),
- style = MaterialTheme.typography.bodyMedium,
- modifier = Modifier.padding(bottom = 16.dp, start = 16.dp)
- )
- } else if (createOptionInfo.totalCredentialCount != null) {
- // TODO: Handle the case when there is total count
- // but no passwords and passkeys after design is set
+ Column() {
+ TextOnSurfaceVariant(
+ text = providerInfo.displayName,
+ style = MaterialTheme.typography.titleLarge,
+ modifier = Modifier.padding(top = 16.dp),
+ )
+ if (createOptionInfo.userProviderDisplayName != null) {
+ TextSecondary(
+ text = createOptionInfo.userProviderDisplayName,
+ style = MaterialTheme.typography.bodyMedium,
+ )
+ }
+ if (createOptionInfo.passwordCount != null &&
+ createOptionInfo.passkeyCount != null
+ ) {
+ TextSecondary(
+ text =
+ stringResource(
+ R.string.more_options_usage_passwords_passkeys,
+ createOptionInfo.passwordCount,
+ createOptionInfo.passkeyCount
+ ),
+ style = MaterialTheme.typography.bodyMedium,
+ modifier = Modifier.padding(bottom = 16.dp),
+ )
+ } else if (createOptionInfo.passwordCount != null) {
+ TextSecondary(
+ text =
+ stringResource(
+ R.string.more_options_usage_passwords,
+ createOptionInfo.passwordCount
+ ),
+ style = MaterialTheme.typography.bodyMedium,
+ modifier = Modifier.padding(bottom = 16.dp),
+ )
+ } else if (createOptionInfo.passkeyCount != null) {
+ TextSecondary(
+ text =
+ stringResource(
+ R.string.more_options_usage_passkeys,
+ createOptionInfo.passkeyCount
+ ),
+ style = MaterialTheme.typography.bodyMedium,
+ modifier = Modifier.padding(bottom = 16.dp),
+ )
+ } else if (createOptionInfo.totalCredentialCount != null) {
+ // TODO: Handle the case when there is total count
+ // but no passwords and passkeys after design is set
+ }
}
- }
}
)
}
@@ -769,61 +873,61 @@ fun MoreOptionsInfoRow(
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MoreOptionsDisabledProvidersRow(
- disabledProviders: List<ProviderInfo>,
- onDisabledPasswordManagerSelected: () -> Unit,
+ disabledProviders: List<ProviderInfo>,
+ onDisabledPasswordManagerSelected: () -> Unit,
) {
- Entry(
- onClick = onDisabledPasswordManagerSelected,
- icon = {
- Icon(
- Icons.Filled.Add,
- contentDescription = null,
- modifier = Modifier.padding(start = 16.dp)
- )
- },
- label = {
- Column() {
- Text(
- text = stringResource(R.string.other_password_manager),
- style = MaterialTheme.typography.titleLarge,
- modifier = Modifier.padding(top = 16.dp, start = 16.dp)
- )
- // TODO: Update the subtitle once design is confirmed
- Text(
- text = disabledProviders.joinToString(separator = ", "){ it.displayName },
- style = MaterialTheme.typography.bodyMedium,
- modifier = Modifier.padding(bottom = 16.dp, start = 16.dp)
- )
- }
- }
- )
+ Entry(
+ onClick = onDisabledPasswordManagerSelected,
+ icon = {
+ Icon(
+ Icons.Filled.Add,
+ contentDescription = null,
+ modifier = Modifier.padding(start = 16.dp)
+ )
+ },
+ label = {
+ Column() {
+ TextOnSurfaceVariant(
+ text = stringResource(R.string.other_password_manager),
+ style = MaterialTheme.typography.titleLarge,
+ modifier = Modifier.padding(top = 16.dp, start = 16.dp),
+ )
+ // TODO: Update the subtitle once design is confirmed
+ TextSecondary(
+ text = disabledProviders.joinToString(separator = ", ") { it.displayName },
+ style = MaterialTheme.typography.bodyMedium,
+ modifier = Modifier.padding(bottom = 16.dp, start = 16.dp),
+ )
+ }
+ }
+ )
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun RemoteEntryRow(
- remoteInfo: RemoteInfo,
- onRemoteEntrySelected: (RemoteInfo) -> Unit,
+ remoteInfo: RemoteInfo,
+ onRemoteEntrySelected: (RemoteInfo) -> Unit,
) {
- Entry(
- onClick = {onRemoteEntrySelected(remoteInfo)},
- icon = {
- Icon(
- painter = painterResource(R.drawable.ic_other_devices),
- contentDescription = null,
- tint = Color.Unspecified,
- modifier = Modifier.padding(start = 18.dp)
- )
- },
- label = {
- Column() {
- Text(
- text = stringResource(R.string.another_device),
- style = MaterialTheme.typography.titleLarge,
- modifier = Modifier.padding(start = 16.dp, top = 18.dp, bottom = 18.dp)
- .align(alignment = Alignment.CenterHorizontally)
- )
- }
- }
- )
+ Entry(
+ onClick = { onRemoteEntrySelected(remoteInfo) },
+ icon = {
+ Icon(
+ painter = painterResource(R.drawable.ic_other_devices),
+ contentDescription = null,
+ tint = Color.Unspecified,
+ modifier = Modifier.padding(start = 18.dp)
+ )
+ },
+ label = {
+ Column() {
+ TextOnSurfaceVariant(
+ text = stringResource(R.string.another_device),
+ style = MaterialTheme.typography.titleLarge,
+ modifier = Modifier.padding(start = 16.dp, top = 18.dp, bottom = 18.dp)
+ .align(alignment = Alignment.CenterHorizontally),
+ )
+ }
+ }
+ )
} \ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt
index 393cf7d9ae01..518aaee5ea9b 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt
@@ -16,6 +16,7 @@
package com.android.credentialmanager.createflow
+import android.app.Activity
import android.util.Log
import androidx.activity.compose.ManagedActivityResultLauncher
import androidx.activity.result.ActivityResult
@@ -26,6 +27,7 @@ import androidx.compose.runtime.setValue
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
+import com.android.credentialmanager.CreateFlowUtils
import com.android.credentialmanager.CredentialManagerRepo
import com.android.credentialmanager.common.DialogResult
import com.android.credentialmanager.common.ProviderActivityResult
@@ -39,6 +41,8 @@ data class CreateCredentialUiState(
val showActiveEntryOnly: Boolean,
val activeEntry: ActiveEntry? = null,
val selectedEntry: EntryInfo? = null,
+ val hidden: Boolean = false,
+ val providerActivityPending: Boolean = false,
)
class CreateCredentialViewModel(
@@ -58,24 +62,26 @@ class CreateCredentialViewModel(
fun onConfirmIntro() {
var createOptionSize = 0
+ var lastSeenProviderWithNonEmptyCreateOptions: EnabledProviderInfo? = null
+ var remoteEntry: RemoteInfo? = null
uiState.enabledProviders.forEach {
- enabledProvider -> createOptionSize += enabledProvider.createOptions.size}
- uiState = if (createOptionSize > 1) {
- uiState.copy(
- currentScreenState = CreateScreenState.PROVIDER_SELECTION,
- showActiveEntryOnly = true
- )
- } else if (createOptionSize == 1){
- uiState.copy(
- currentScreenState = CreateScreenState.CREATION_OPTION_SELECTION,
- showActiveEntryOnly = false,
- activeEntry = ActiveEntry(uiState.enabledProviders.first(),
- uiState.enabledProviders.first().createOptions.first()
- )
- )
- } else {
- throw java.lang.IllegalStateException("Empty provider list.")
+ enabledProvider ->
+ if (enabledProvider.createOptions.isNotEmpty()) {
+ createOptionSize += enabledProvider.createOptions.size
+ lastSeenProviderWithNonEmptyCreateOptions = enabledProvider
+ }
+ if (enabledProvider.remoteEntry != null) {
+ remoteEntry = enabledProvider.remoteEntry!!
+ }
}
+ uiState = uiState.copy(
+ currentScreenState = CreateFlowUtils.toCreateScreenState(
+ createOptionSize, true,
+ uiState.requestDisplayInfo, null, remoteEntry),
+ showActiveEntryOnly = createOptionSize > 1,
+ activeEntry = CreateFlowUtils.toActiveEntry(
+ null, createOptionSize, lastSeenProviderWithNonEmptyCreateOptions, remoteEntry),
+ )
}
fun getProviderInfoByName(providerName: String): EnabledProviderInfo {
@@ -128,10 +134,7 @@ class CreateCredentialViewModel(
// TODO: implement the if choose as default or not logic later
}
- fun onEntrySelected(
- selectedEntry: EntryInfo,
- launcher: ManagedActivityResultLauncher<IntentSenderRequest, ActivityResult>
- ) {
+ fun onEntrySelected(selectedEntry: EntryInfo) {
val providerId = selectedEntry.providerId
val entryKey = selectedEntry.entryKey
val entrySubkey = selectedEntry.entrySubkey
@@ -139,10 +142,10 @@ class CreateCredentialViewModel(
"Account Selector", "Option selected for entry: " +
" {provider=$providerId, key=$entryKey, subkey=$entrySubkey")
if (selectedEntry.pendingIntent != null) {
- uiState = uiState.copy(selectedEntry = selectedEntry)
- val intentSenderRequest = IntentSenderRequest.Builder(selectedEntry.pendingIntent)
- .setFillInIntent(selectedEntry.fillInIntent).build()
- launcher.launch(intentSenderRequest)
+ uiState = uiState.copy(
+ selectedEntry = selectedEntry,
+ hidden = true,
+ )
} else {
CredentialManagerRepo.getInstance().onOptionSelected(
providerId,
@@ -155,12 +158,26 @@ class CreateCredentialViewModel(
}
}
- fun onConfirmEntrySelected(
+ fun launchProviderUi(
launcher: ManagedActivityResultLauncher<IntentSenderRequest, ActivityResult>
) {
+ val entry = uiState.selectedEntry
+ if (entry != null && entry.pendingIntent != null) {
+ uiState = uiState.copy(
+ providerActivityPending = true,
+ )
+ val intentSenderRequest = IntentSenderRequest.Builder(entry.pendingIntent)
+ .setFillInIntent(entry.fillInIntent).build()
+ launcher.launch(intentSenderRequest)
+ } else {
+ Log.w("Account Selector", "No provider UI to launch")
+ }
+ }
+
+ fun onConfirmEntrySelected() {
val selectedEntry = uiState.activeEntry?.activeEntryInfo
if (selectedEntry != null) {
- onEntrySelected(selectedEntry, launcher)
+ onEntrySelected(selectedEntry)
} else {
Log.w("Account Selector",
"Illegal state: confirm is pressed but activeEntry isn't set.")
@@ -174,21 +191,30 @@ class CreateCredentialViewModel(
val entry = uiState.selectedEntry
val resultCode = providerActivityResult.resultCode
val resultData = providerActivityResult.data
- if (entry != null) {
- val providerId = entry.providerId
- Log.d("Account Selector", "Got provider activity result: {provider=" +
- "$providerId, key=${entry.entryKey}, subkey=${entry.entrySubkey}, " +
- "resultCode=$resultCode, resultData=$resultData}"
- )
- CredentialManagerRepo.getInstance().onOptionSelected(
- providerId, entry.entryKey, entry.entrySubkey, resultCode, resultData,
+ if (resultCode == Activity.RESULT_CANCELED) {
+ // Re-display the CredMan UI if the user canceled from the provider UI.
+ uiState = uiState.copy(
+ selectedEntry = null,
+ hidden = false,
+ providerActivityPending = false,
)
} else {
- Log.w("Account Selector",
- "Illegal state: received a provider result but found no matching entry.")
+ if (entry != null) {
+ val providerId = entry.providerId
+ Log.d("Account Selector", "Got provider activity result: {provider=" +
+ "$providerId, key=${entry.entryKey}, subkey=${entry.entrySubkey}, " +
+ "resultCode=$resultCode, resultData=$resultData}"
+ )
+ CredentialManagerRepo.getInstance().onOptionSelected(
+ providerId, entry.entryKey, entry.entrySubkey, resultCode, resultData,
+ )
+ } else {
+ Log.w("Account Selector",
+ "Illegal state: received a provider result but found no matching entry.")
+ }
+ dialogResult.value = DialogResult(
+ ResultState.COMPLETE,
+ )
}
- dialogResult.value = DialogResult(
- ResultState.COMPLETE,
- )
}
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
index 9ac524a4e6d9..21abe08c04f2 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
@@ -77,6 +77,7 @@ data class RequestDisplayInfo(
val type: String,
val appDomainName: String,
val typeIcon: Drawable,
+ val isFirstUsage: Boolean,
)
/**
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
index 720f231609c3..d6d71220e943 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
@@ -22,6 +22,7 @@ import androidx.activity.result.ActivityResult
import androidx.activity.result.IntentSenderRequest
import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
@@ -31,13 +32,11 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
-import androidx.compose.material3.Card
import androidx.compose.material3.Divider
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material.icons.Icons
@@ -59,202 +58,217 @@ import com.android.credentialmanager.common.material.ModalBottomSheetValue
import com.android.credentialmanager.common.material.rememberModalBottomSheetState
import com.android.credentialmanager.common.ui.CancelButton
import com.android.credentialmanager.common.ui.Entry
+import com.android.credentialmanager.common.ui.TextOnSurface
+import com.android.credentialmanager.common.ui.TextSecondary
+import com.android.credentialmanager.common.ui.TextOnSurfaceVariant
+import com.android.credentialmanager.common.ui.ContainerCard
import com.android.credentialmanager.common.ui.TransparentBackgroundEntry
import com.android.credentialmanager.jetpack.developer.PublicKeyCredential
import com.android.credentialmanager.ui.theme.EntryShape
@Composable
fun GetCredentialScreen(
- viewModel: GetCredentialViewModel,
- providerActivityLauncher: ManagedActivityResultLauncher<IntentSenderRequest, ActivityResult>
+ viewModel: GetCredentialViewModel,
+ providerActivityLauncher: ManagedActivityResultLauncher<IntentSenderRequest, ActivityResult>
) {
- val entrySelectionCallback: (EntryInfo) -> Unit = {
- viewModel.onEntrySelected(it, providerActivityLauncher)
- }
- val state = rememberModalBottomSheetState(
- initialValue = ModalBottomSheetValue.Expanded,
- skipHalfExpanded = true
- )
- ModalBottomSheetLayout(
- sheetState = state,
- sheetContent = {
- val uiState = viewModel.uiState
- when (uiState.currentScreenState) {
- GetScreenState.PRIMARY_SELECTION -> PrimarySelectionCard(
- requestDisplayInfo = uiState.requestDisplayInfo,
- providerDisplayInfo = uiState.providerDisplayInfo,
- onEntrySelected = entrySelectionCallback,
- onCancel = viewModel::onCancel,
- onMoreOptionSelected = viewModel::onMoreOptionSelected,
- )
- GetScreenState.ALL_SIGN_IN_OPTIONS -> AllSignInOptionCard(
- providerInfoList = uiState.providerInfoList,
- providerDisplayInfo = uiState.providerDisplayInfo,
- onEntrySelected = entrySelectionCallback,
- onBackButtonClicked = viewModel::onBackToPrimarySelectionScreen,
- )
- }
- },
- scrimColor = MaterialTheme.colorScheme.scrim.copy(alpha = 0.8f),
- sheetShape = EntryShape.TopRoundedCorner,
- ) {}
- LaunchedEffect(state.currentValue) {
- if (state.currentValue == ModalBottomSheetValue.Hidden) {
- viewModel.onCancel()
+ val state = rememberModalBottomSheetState(
+ initialValue = ModalBottomSheetValue.Expanded,
+ skipHalfExpanded = true
+ )
+ ModalBottomSheetLayout(
+ sheetBackgroundColor = MaterialTheme.colorScheme.surface,
+ modifier = Modifier.background(Color.Transparent),
+ sheetState = state,
+ sheetContent = {
+ val uiState = viewModel.uiState
+ if (!uiState.hidden) {
+ when (uiState.currentScreenState) {
+ GetScreenState.PRIMARY_SELECTION -> PrimarySelectionCard(
+ requestDisplayInfo = uiState.requestDisplayInfo,
+ providerDisplayInfo = uiState.providerDisplayInfo,
+ onEntrySelected = viewModel::onEntrySelected,
+ onCancel = viewModel::onCancel,
+ onMoreOptionSelected = viewModel::onMoreOptionSelected,
+ )
+ GetScreenState.ALL_SIGN_IN_OPTIONS -> AllSignInOptionCard(
+ providerInfoList = uiState.providerInfoList,
+ providerDisplayInfo = uiState.providerDisplayInfo,
+ onEntrySelected = viewModel::onEntrySelected,
+ onBackButtonClicked = viewModel::onBackToPrimarySelectionScreen,
+ )
+ }
+ } else if (uiState.selectedEntry != null && !uiState.providerActivityPending) {
+ viewModel.launchProviderUi(providerActivityLauncher)
+ }
+ },
+ scrimColor = MaterialTheme.colorScheme.scrim.copy(alpha = 0.8f),
+ sheetShape = EntryShape.TopRoundedCorner,
+ ) {}
+ LaunchedEffect(state.currentValue) {
+ if (state.currentValue == ModalBottomSheetValue.Hidden) {
+ viewModel.onCancel()
+ }
}
- }
}
/** Draws the primary credential selection page. */
@Composable
fun PrimarySelectionCard(
- requestDisplayInfo: RequestDisplayInfo,
- providerDisplayInfo: ProviderDisplayInfo,
- onEntrySelected: (EntryInfo) -> Unit,
- onCancel: () -> Unit,
- onMoreOptionSelected: () -> Unit,
+ requestDisplayInfo: RequestDisplayInfo,
+ providerDisplayInfo: ProviderDisplayInfo,
+ onEntrySelected: (EntryInfo) -> Unit,
+ onCancel: () -> Unit,
+ onMoreOptionSelected: () -> Unit,
) {
- val sortedUserNameToCredentialEntryList = providerDisplayInfo.sortedUserNameToCredentialEntryList
- val authenticationEntryList = providerDisplayInfo.authenticationEntryList
- Card() {
- Column() {
- Text(
- modifier = Modifier.padding(all = 24.dp),
- textAlign = TextAlign.Center,
- style = MaterialTheme.typography.headlineSmall,
- text = stringResource(
- if (sortedUserNameToCredentialEntryList.size == 1) {
- if (sortedUserNameToCredentialEntryList.first().sortedCredentialEntryList
- .first().credentialType == PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL
+ val sortedUserNameToCredentialEntryList =
+ providerDisplayInfo.sortedUserNameToCredentialEntryList
+ val authenticationEntryList = providerDisplayInfo.authenticationEntryList
+ ContainerCard() {
+ Column() {
+ TextOnSurface(
+ modifier = Modifier.padding(all = 24.dp),
+ textAlign = TextAlign.Center,
+ style = MaterialTheme.typography.headlineSmall,
+ text = stringResource(
+ if (sortedUserNameToCredentialEntryList.size == 1) {
+ if (sortedUserNameToCredentialEntryList.first().sortedCredentialEntryList
+ .first().credentialType
+ == PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL
+ )
+ R.string.get_dialog_title_use_passkey_for
+ else R.string.get_dialog_title_use_sign_in_for
+ } else R.string.get_dialog_title_choose_sign_in_for,
+ requestDisplayInfo.appDomainName
+ ),
)
- R.string.get_dialog_title_use_passkey_for
- else R.string.get_dialog_title_use_sign_in_for
- } else R.string.get_dialog_title_choose_sign_in_for,
- requestDisplayInfo.appDomainName
- ),
- )
- Card(
- shape = MaterialTheme.shapes.medium,
- modifier = Modifier
- .padding(horizontal = 24.dp)
- .align(alignment = Alignment.CenterHorizontally)
- ) {
- LazyColumn(
- verticalArrangement = Arrangement.spacedBy(2.dp)
- ) {
- items(sortedUserNameToCredentialEntryList) {
- CredentialEntryRow(
- credentialEntryInfo = it.sortedCredentialEntryList.first(),
- onEntrySelected = onEntrySelected,
+ ContainerCard(
+ shape = MaterialTheme.shapes.medium,
+ modifier = Modifier
+ .padding(horizontal = 24.dp)
+ .align(alignment = Alignment.CenterHorizontally)
+ ) {
+ LazyColumn(
+ verticalArrangement = Arrangement.spacedBy(2.dp)
+ ) {
+ items(sortedUserNameToCredentialEntryList) {
+ CredentialEntryRow(
+ credentialEntryInfo = it.sortedCredentialEntryList.first(),
+ onEntrySelected = onEntrySelected,
+ )
+ }
+ items(authenticationEntryList) {
+ AuthenticationEntryRow(
+ authenticationEntryInfo = it,
+ onEntrySelected = onEntrySelected,
+ )
+ }
+ item {
+ SignInAnotherWayRow(onSelect = onMoreOptionSelected)
+ }
+ }
+ }
+ Divider(
+ thickness = 24.dp,
+ color = Color.Transparent
)
- }
- items(authenticationEntryList) {
- AuthenticationEntryRow(
- authenticationEntryInfo = it,
- onEntrySelected = onEntrySelected,
+ Row(
+ horizontalArrangement = Arrangement.Start,
+ modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
+ ) {
+ CancelButton(stringResource(R.string.get_dialog_button_label_no_thanks), onCancel)
+ }
+ Divider(
+ thickness = 18.dp,
+ color = Color.Transparent,
+ modifier = Modifier.padding(bottom = 16.dp)
)
- }
- item {
- SignInAnotherWayRow(onSelect = onMoreOptionSelected)
- }
}
- }
- Divider(
- thickness = 24.dp,
- color = Color.Transparent
- )
- Row(
- horizontalArrangement = Arrangement.Start,
- modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
- ) {
- CancelButton(stringResource(R.string.get_dialog_button_label_no_thanks), onCancel)
- }
- Divider(
- thickness = 18.dp,
- color = Color.Transparent,
- modifier = Modifier.padding(bottom = 16.dp)
- )
}
- }
}
/** Draws the secondary credential selection page, where all sign-in options are listed. */
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AllSignInOptionCard(
- providerInfoList: List<ProviderInfo>,
- providerDisplayInfo: ProviderDisplayInfo,
- onEntrySelected: (EntryInfo) -> Unit,
- onBackButtonClicked: () -> Unit,
+ providerInfoList: List<ProviderInfo>,
+ providerDisplayInfo: ProviderDisplayInfo,
+ onEntrySelected: (EntryInfo) -> Unit,
+ onBackButtonClicked: () -> Unit,
) {
- val sortedUserNameToCredentialEntryList = providerDisplayInfo.sortedUserNameToCredentialEntryList
- val authenticationEntryList = providerDisplayInfo.authenticationEntryList
- Card() {
- Column() {
- TopAppBar(
- colors = TopAppBarDefaults.smallTopAppBarColors(
- containerColor = Color.Transparent,
- ),
- title = {
- Text(
- text = stringResource(R.string.get_dialog_title_sign_in_options),
- style = MaterialTheme.typography.titleMedium
- )
- },
- navigationIcon = {
- IconButton(onClick = onBackButtonClicked) {
- Icon(
- Icons.Filled.ArrowBack,
- contentDescription = stringResource(R.string.accessibility_back_arrow_button))
- }
- },
- modifier = Modifier.padding(top = 12.dp)
- )
-
- Card(
- shape = MaterialTheme.shapes.large,
- modifier = Modifier
- .padding(start = 24.dp, end = 24.dp, bottom = 24.dp)
- .align(alignment = Alignment.CenterHorizontally)
- ) {
- LazyColumn(
- verticalArrangement = Arrangement.spacedBy(8.dp)
- ) {
- // For username
- items(sortedUserNameToCredentialEntryList) { item ->
- PerUserNameCredentials(
- perUserNameCredentialEntryList = item,
- onEntrySelected = onEntrySelected,
+ val sortedUserNameToCredentialEntryList =
+ providerDisplayInfo.sortedUserNameToCredentialEntryList
+ val authenticationEntryList = providerDisplayInfo.authenticationEntryList
+ ContainerCard() {
+ Column() {
+ TopAppBar(
+ colors = TopAppBarDefaults.smallTopAppBarColors(
+ containerColor = Color.Transparent,
+ ),
+ title = {
+ TextOnSurface(
+ text = stringResource(R.string.get_dialog_title_sign_in_options),
+ style = MaterialTheme.typography.titleMedium
+ )
+ },
+ navigationIcon = {
+ IconButton(onClick = onBackButtonClicked) {
+ Icon(
+ Icons.Filled.ArrowBack,
+ contentDescription = stringResource(
+ R.string.accessibility_back_arrow_button)
+ )
+ }
+ },
+ modifier = Modifier.padding(top = 12.dp)
)
- }
- // Locked password manager
- if (!authenticationEntryList.isEmpty()) {
- item {
- LockedCredentials(
- authenticationEntryList = authenticationEntryList,
- onEntrySelected = onEntrySelected,
- )
- }
- }
- // From another device
- val remoteEntry = providerDisplayInfo.remoteEntry
- if (remoteEntry != null) {
- item {
- RemoteEntryCard(
- remoteEntry = remoteEntry,
- onEntrySelected = onEntrySelected,
- )
+
+ ContainerCard(
+ shape = MaterialTheme.shapes.large,
+ modifier = Modifier
+ .padding(start = 24.dp, end = 24.dp, bottom = 24.dp)
+ .align(alignment = Alignment.CenterHorizontally),
+ ) {
+ LazyColumn(
+ verticalArrangement = Arrangement.spacedBy(8.dp)
+ ) {
+ // For username
+ items(sortedUserNameToCredentialEntryList) { item ->
+ PerUserNameCredentials(
+ perUserNameCredentialEntryList = item,
+ onEntrySelected = onEntrySelected,
+ )
+ }
+ // Locked password manager
+ if (!authenticationEntryList.isEmpty()) {
+ item {
+ LockedCredentials(
+ authenticationEntryList = authenticationEntryList,
+ onEntrySelected = onEntrySelected,
+ )
+ }
+ }
+ // From another device
+ val remoteEntry = providerDisplayInfo.remoteEntry
+ if (remoteEntry != null) {
+ item {
+ RemoteEntryCard(
+ remoteEntry = remoteEntry,
+ onEntrySelected = onEntrySelected,
+ )
+ }
+ }
+ // Manage sign-ins (action chips)
+ item {
+ ActionChips(
+ providerInfoList = providerInfoList,
+ onEntrySelected = onEntrySelected
+ )
+ }
+ }
}
- }
- // Manage sign-ins (action chips)
- item {
- ActionChips(providerInfoList = providerInfoList, onEntrySelected = onEntrySelected)
- }
}
- }
}
- }
}
// TODO: create separate rows for primary and secondary pages.
@@ -262,236 +276,245 @@ fun AllSignInOptionCard(
@Composable
fun ActionChips(
- providerInfoList: List<ProviderInfo>,
- onEntrySelected: (EntryInfo) -> Unit,
+ providerInfoList: List<ProviderInfo>,
+ onEntrySelected: (EntryInfo) -> Unit,
) {
- val actionChips = providerInfoList.flatMap { it.actionEntryList }
- if (actionChips.isEmpty()) {
- return
- }
+ val actionChips = providerInfoList.flatMap { it.actionEntryList }
+ if (actionChips.isEmpty()) {
+ return
+ }
- Text(
- text = stringResource(R.string.get_dialog_heading_manage_sign_ins),
- style = MaterialTheme.typography.labelLarge,
- modifier = Modifier.padding(vertical = 8.dp)
- )
- // TODO: tweak padding.
- Card(
- modifier = Modifier.fillMaxWidth().wrapContentHeight(),
- shape = MaterialTheme.shapes.medium,
- ) {
- Column(verticalArrangement = Arrangement.spacedBy(2.dp)) {
- actionChips.forEach {
- ActionEntryRow(it, onEntrySelected)
- }
+ TextSecondary(
+ text = stringResource(R.string.get_dialog_heading_manage_sign_ins),
+ style = MaterialTheme.typography.labelLarge,
+ modifier = Modifier.padding(vertical = 8.dp)
+ )
+ // TODO: tweak padding.
+ ContainerCard(
+ modifier = Modifier.fillMaxWidth().wrapContentHeight(),
+ shape = MaterialTheme.shapes.medium,
+ ) {
+ Column(verticalArrangement = Arrangement.spacedBy(2.dp)) {
+ actionChips.forEach {
+ ActionEntryRow(it, onEntrySelected)
+ }
+ }
}
- }
}
@Composable
fun RemoteEntryCard(
- remoteEntry: RemoteEntryInfo,
- onEntrySelected: (EntryInfo) -> Unit,
+ remoteEntry: RemoteEntryInfo,
+ onEntrySelected: (EntryInfo) -> Unit,
) {
- Text(
- text = stringResource(R.string.get_dialog_heading_from_another_device),
- style = MaterialTheme.typography.labelLarge,
- modifier = Modifier.padding(vertical = 8.dp)
- )
- Card(
- modifier = Modifier.fillMaxWidth().wrapContentHeight(),
- shape = MaterialTheme.shapes.medium,
- ) {
- Column(
- modifier = Modifier.fillMaxWidth().wrapContentHeight(),
- verticalArrangement = Arrangement.spacedBy(2.dp),
+ TextSecondary(
+ text = stringResource(R.string.get_dialog_heading_from_another_device),
+ style = MaterialTheme.typography.labelLarge,
+ modifier = Modifier.padding(vertical = 8.dp)
+ )
+ ContainerCard(
+ modifier = Modifier.fillMaxWidth().wrapContentHeight(),
+ shape = MaterialTheme.shapes.medium,
) {
- Entry(
- onClick = {onEntrySelected(remoteEntry)},
- icon = {
- Icon(
- painter = painterResource(R.drawable.ic_other_devices),
- contentDescription = null,
- tint = Color.Unspecified,
- modifier = Modifier.padding(start = 18.dp)
- )
- },
- label = {
- Text(
- text = stringResource(R.string.get_dialog_option_headline_use_a_different_device),
- style = MaterialTheme.typography.titleLarge,
- modifier = Modifier.padding(start = 16.dp, top = 18.dp, bottom = 18.dp)
- .align(alignment = Alignment.CenterHorizontally)
- )
+ Column(
+ modifier = Modifier.fillMaxWidth().wrapContentHeight(),
+ verticalArrangement = Arrangement.spacedBy(2.dp),
+ ) {
+ Entry(
+ onClick = { onEntrySelected(remoteEntry) },
+ icon = {
+ Icon(
+ painter = painterResource(R.drawable.ic_other_devices),
+ contentDescription = null,
+ tint = Color.Unspecified,
+ modifier = Modifier.padding(start = 18.dp)
+ )
+ },
+ label = {
+ TextOnSurfaceVariant(
+ text = stringResource(
+ R.string.get_dialog_option_headline_use_a_different_device),
+ style = MaterialTheme.typography.titleLarge,
+ modifier = Modifier.padding(start = 16.dp, top = 18.dp, bottom = 18.dp)
+ .align(alignment = Alignment.CenterHorizontally)
+ )
+ }
+ )
}
- )
}
- }
}
@Composable
fun LockedCredentials(
- authenticationEntryList: List<AuthenticationEntryInfo>,
- onEntrySelected: (EntryInfo) -> Unit,
+ authenticationEntryList: List<AuthenticationEntryInfo>,
+ onEntrySelected: (EntryInfo) -> Unit,
) {
- Text(
- text = stringResource(R.string.get_dialog_heading_locked_password_managers),
- style = MaterialTheme.typography.labelLarge,
- modifier = Modifier.padding(vertical = 8.dp)
- )
- Card(
- modifier = Modifier.fillMaxWidth().wrapContentHeight(),
- shape = MaterialTheme.shapes.medium,
- ) {
- Column(
- modifier = Modifier.fillMaxWidth().wrapContentHeight(),
- verticalArrangement = Arrangement.spacedBy(2.dp),
+ TextSecondary(
+ text = stringResource(R.string.get_dialog_heading_locked_password_managers),
+ style = MaterialTheme.typography.labelLarge,
+ modifier = Modifier.padding(vertical = 8.dp)
+ )
+ ContainerCard(
+ modifier = Modifier.fillMaxWidth().wrapContentHeight(),
+ shape = MaterialTheme.shapes.medium,
) {
- authenticationEntryList.forEach {
- AuthenticationEntryRow(it, onEntrySelected)
- }
+ Column(
+ modifier = Modifier.fillMaxWidth().wrapContentHeight(),
+ verticalArrangement = Arrangement.spacedBy(2.dp),
+ ) {
+ authenticationEntryList.forEach {
+ AuthenticationEntryRow(it, onEntrySelected)
+ }
+ }
}
- }
}
@Composable
fun PerUserNameCredentials(
- perUserNameCredentialEntryList: PerUserNameCredentialEntryList,
- onEntrySelected: (EntryInfo) -> Unit,
+ perUserNameCredentialEntryList: PerUserNameCredentialEntryList,
+ onEntrySelected: (EntryInfo) -> Unit,
) {
- Text(
- text = stringResource(
- R.string.get_dialog_heading_for_username, perUserNameCredentialEntryList.userName),
- style = MaterialTheme.typography.labelLarge,
- modifier = Modifier.padding(vertical = 8.dp)
- )
- Card(
- modifier = Modifier.fillMaxWidth().wrapContentHeight(),
- shape = MaterialTheme.shapes.medium,
- ) {
- Column(
- modifier = Modifier.fillMaxWidth().wrapContentHeight(),
- verticalArrangement = Arrangement.spacedBy(2.dp),
+ TextSecondary(
+ text = stringResource(
+ R.string.get_dialog_heading_for_username, perUserNameCredentialEntryList.userName
+ ),
+ style = MaterialTheme.typography.labelLarge,
+ modifier = Modifier.padding(vertical = 8.dp)
+ )
+ ContainerCard(
+ modifier = Modifier.fillMaxWidth().wrapContentHeight(),
+ shape = MaterialTheme.shapes.medium,
) {
- perUserNameCredentialEntryList.sortedCredentialEntryList.forEach {
- CredentialEntryRow(it, onEntrySelected)
- }
+ Column(
+ modifier = Modifier.fillMaxWidth().wrapContentHeight(),
+ verticalArrangement = Arrangement.spacedBy(2.dp),
+ ) {
+ perUserNameCredentialEntryList.sortedCredentialEntryList.forEach {
+ CredentialEntryRow(it, onEntrySelected)
+ }
+ }
}
- }
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun CredentialEntryRow(
- credentialEntryInfo: CredentialEntryInfo,
- onEntrySelected: (EntryInfo) -> Unit,
+ credentialEntryInfo: CredentialEntryInfo,
+ onEntrySelected: (EntryInfo) -> Unit,
) {
- Entry(
- onClick = {onEntrySelected(credentialEntryInfo)},
- icon = {
- Image(modifier = Modifier.padding(start = 10.dp).size(32.dp),
- bitmap = credentialEntryInfo.icon.toBitmap().asImageBitmap(),
- // TODO: add description.
- contentDescription = "")
- },
- label = {
- Column() {
- // TODO: fix the text values.
- Text(
- text = credentialEntryInfo.userName,
- style = MaterialTheme.typography.titleLarge,
- modifier = Modifier.padding(top = 16.dp)
- )
- Text(
- text =
- if (TextUtils.isEmpty(credentialEntryInfo.displayName))
- credentialEntryInfo.credentialTypeDisplayName
- else
- credentialEntryInfo.credentialTypeDisplayName +
- stringResource(R.string.get_dialog_sign_in_type_username_separator) +
- credentialEntryInfo.displayName,
- style = MaterialTheme.typography.bodyMedium,
- modifier = Modifier.padding(bottom = 16.dp)
- )
- }
- }
- )
+ Entry(
+ onClick = { onEntrySelected(credentialEntryInfo) },
+ icon = {
+ Image(
+ modifier = Modifier.padding(start = 10.dp).size(32.dp),
+ bitmap = credentialEntryInfo.icon.toBitmap().asImageBitmap(),
+ // TODO: add description.
+ contentDescription = ""
+ )
+ },
+ label = {
+ Column() {
+ // TODO: fix the text values.
+ TextOnSurfaceVariant(
+ text = credentialEntryInfo.userName,
+ style = MaterialTheme.typography.titleLarge,
+ modifier = Modifier.padding(top = 16.dp)
+ )
+ TextSecondary(
+ text =
+ if (TextUtils.isEmpty(credentialEntryInfo.displayName))
+ credentialEntryInfo.credentialTypeDisplayName
+ else
+ credentialEntryInfo.credentialTypeDisplayName +
+ stringResource(
+ R.string.get_dialog_sign_in_type_username_separator) +
+ credentialEntryInfo.displayName,
+ style = MaterialTheme.typography.bodyMedium,
+ modifier = Modifier.padding(bottom = 16.dp)
+ )
+ }
+ }
+ )
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AuthenticationEntryRow(
- authenticationEntryInfo: AuthenticationEntryInfo,
- onEntrySelected: (EntryInfo) -> Unit,
+ authenticationEntryInfo: AuthenticationEntryInfo,
+ onEntrySelected: (EntryInfo) -> Unit,
) {
- Entry(
- onClick = {onEntrySelected(authenticationEntryInfo)},
- icon = {
- Image(modifier = Modifier.padding(start = 10.dp).size(32.dp),
- bitmap = authenticationEntryInfo.icon.toBitmap().asImageBitmap(),
- // TODO: add description.
- contentDescription = "")
- },
- label = {
- Column() {
- // TODO: fix the text values.
- Text(
- text = authenticationEntryInfo.title,
- style = MaterialTheme.typography.titleLarge,
- modifier = Modifier.padding(top = 16.dp)
- )
- Text(
- text = stringResource(R.string.locked_credential_entry_label_subtext),
- style = MaterialTheme.typography.bodyMedium,
- modifier = Modifier.padding(bottom = 16.dp)
- )
- }
- }
- )
+ Entry(
+ onClick = { onEntrySelected(authenticationEntryInfo) },
+ icon = {
+ Image(
+ modifier = Modifier.padding(start = 10.dp).size(32.dp),
+ bitmap = authenticationEntryInfo.icon.toBitmap().asImageBitmap(),
+ // TODO: add description.
+ contentDescription = ""
+ )
+ },
+ label = {
+ Column() {
+ // TODO: fix the text values.
+ TextOnSurfaceVariant(
+ text = authenticationEntryInfo.title,
+ style = MaterialTheme.typography.titleLarge,
+ modifier = Modifier.padding(top = 16.dp)
+ )
+ TextSecondary(
+ text = stringResource(R.string.locked_credential_entry_label_subtext),
+ style = MaterialTheme.typography.bodyMedium,
+ modifier = Modifier.padding(bottom = 16.dp)
+ )
+ }
+ }
+ )
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ActionEntryRow(
- actionEntryInfo: ActionEntryInfo,
- onEntrySelected: (EntryInfo) -> Unit,
+ actionEntryInfo: ActionEntryInfo,
+ onEntrySelected: (EntryInfo) -> Unit,
) {
- TransparentBackgroundEntry(
- icon = {
- Image(modifier = Modifier.padding(start = 10.dp).size(32.dp),
- bitmap = actionEntryInfo.icon.toBitmap().asImageBitmap(),
- // TODO: add description.
- contentDescription = "")
- },
- label = {
- Column() {
- Text(
- text = actionEntryInfo.title,
- style = MaterialTheme.typography.titleLarge,
- )
- if (actionEntryInfo.subTitle != null) {
- Text(
- text = actionEntryInfo.subTitle,
- style = MaterialTheme.typography.bodyMedium,
- )
- }
- }
- },
- onClick = { onEntrySelected(actionEntryInfo) },
- )
+ TransparentBackgroundEntry(
+ icon = {
+ Image(
+ modifier = Modifier.padding(start = 10.dp).size(32.dp),
+ bitmap = actionEntryInfo.icon.toBitmap().asImageBitmap(),
+ // TODO: add description.
+ contentDescription = ""
+ )
+ },
+ label = {
+ Column() {
+ TextOnSurfaceVariant(
+ text = actionEntryInfo.title,
+ style = MaterialTheme.typography.titleLarge,
+ )
+ if (actionEntryInfo.subTitle != null) {
+ TextSecondary(
+ text = actionEntryInfo.subTitle,
+ style = MaterialTheme.typography.bodyMedium,
+ )
+ }
+ }
+ },
+ onClick = { onEntrySelected(actionEntryInfo) },
+ )
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SignInAnotherWayRow(onSelect: () -> Unit) {
- Entry(
- onClick = onSelect,
- label = {
- Text(
- text = stringResource(R.string.get_dialog_use_saved_passkey_for),
- style = MaterialTheme.typography.titleLarge,
- modifier = Modifier.padding(vertical = 16.dp)
- )
- }
- )
+ Entry(
+ onClick = onSelect,
+ label = {
+ TextOnSurfaceVariant(
+ text = stringResource(R.string.get_dialog_use_saved_passkey_for),
+ style = MaterialTheme.typography.titleLarge,
+ modifier = Modifier.padding(vertical = 16.dp)
+ )
+ }
+ )
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt
index 6dea9c299989..7b80124d618c 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt
@@ -16,6 +16,7 @@
package com.android.credentialmanager.getflow
+import android.app.Activity
import android.util.Log
import androidx.activity.compose.ManagedActivityResultLauncher
import androidx.activity.result.ActivityResult
@@ -39,6 +40,8 @@ data class GetCredentialUiState(
val requestDisplayInfo: RequestDisplayInfo,
val providerDisplayInfo: ProviderDisplayInfo = toProviderDisplayInfo(providerInfoList),
val selectedEntry: EntryInfo? = null,
+ val hidden: Boolean = false,
+ val providerActivityPending: Boolean = false,
)
class GetCredentialViewModel(
@@ -56,17 +59,14 @@ class GetCredentialViewModel(
return dialogResult
}
- fun onEntrySelected(
- entry: EntryInfo,
- launcher: ManagedActivityResultLauncher<IntentSenderRequest, ActivityResult>
- ) {
+ fun onEntrySelected(entry: EntryInfo) {
Log.d("Account Selector", "credential selected:" +
" {provider=${entry.providerId}, key=${entry.entryKey}, subkey=${entry.entrySubkey}}")
if (entry.pendingIntent != null) {
- uiState = uiState.copy(selectedEntry = entry)
- val intentSenderRequest = IntentSenderRequest.Builder(entry.pendingIntent)
- .setFillInIntent(entry.fillInIntent).build()
- launcher.launch(intentSenderRequest)
+ uiState = uiState.copy(
+ selectedEntry = entry,
+ hidden = true,
+ )
} else {
CredentialManagerRepo.getInstance().onOptionSelected(
entry.providerId, entry.entryKey, entry.entrySubkey,
@@ -75,24 +75,49 @@ class GetCredentialViewModel(
}
}
+ fun launchProviderUi(
+ launcher: ManagedActivityResultLauncher<IntentSenderRequest, ActivityResult>
+ ) {
+ val entry = uiState.selectedEntry
+ if (entry != null && entry.pendingIntent != null) {
+ uiState = uiState.copy(
+ providerActivityPending = true,
+ )
+ val intentSenderRequest = IntentSenderRequest.Builder(entry.pendingIntent)
+ .setFillInIntent(entry.fillInIntent).build()
+ launcher.launch(intentSenderRequest)
+ } else {
+ Log.w("Account Selector", "No provider UI to launch")
+ }
+ }
+
fun onProviderActivityResult(providerActivityResult: ProviderActivityResult) {
val entry = uiState.selectedEntry
val resultCode = providerActivityResult.resultCode
val resultData = providerActivityResult.data
- if (entry != null) {
- Log.d("Account Selector", "Got provider activity result: {provider=" +
- "${entry.providerId}, key=${entry.entryKey}, subkey=${entry.entrySubkey}, " +
- "resultCode=$resultCode, resultData=$resultData}"
- )
- CredentialManagerRepo.getInstance().onOptionSelected(
- entry.providerId, entry.entryKey, entry.entrySubkey,
- resultCode, resultData,
+ if (resultCode == Activity.RESULT_CANCELED) {
+ // Re-display the CredMan UI if the user canceled from the provider UI.
+ uiState = uiState.copy(
+ selectedEntry = null,
+ hidden = false,
+ providerActivityPending = false,
)
} else {
- Log.w("Account Selector",
- "Illegal state: received a provider result but found no matching entry.")
+ if (entry != null) {
+ Log.d("Account Selector", "Got provider activity result: {provider=" +
+ "${entry.providerId}, key=${entry.entryKey}, subkey=${entry.entrySubkey}, " +
+ "resultCode=$resultCode, resultData=$resultData}"
+ )
+ CredentialManagerRepo.getInstance().onOptionSelected(
+ entry.providerId, entry.entryKey, entry.entrySubkey,
+ resultCode, resultData,
+ )
+ } else {
+ Log.w("Account Selector",
+ "Illegal state: received a provider result but found no matching entry.")
+ }
+ dialogResult.value = DialogResult(ResultState.COMPLETE)
}
- dialogResult.value = DialogResult(ResultState.COMPLETE)
}
fun onMoreOptionSelected() {
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCredentialOption.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCredentialOption.kt
index eb65241ed4df..ef48a778a007 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCredentialOption.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCredentialOption.kt
@@ -28,9 +28,9 @@ import android.os.Bundle
* otherwise
*/
open class GetCredentialOption(
- val type: String,
- val data: Bundle,
- val requireSystemProvider: Boolean,
+ val type: String,
+ val data: Bundle,
+ val requireSystemProvider: Boolean,
) {
companion object {
@JvmStatic
@@ -38,14 +38,20 @@ open class GetCredentialOption(
return try {
when (from.type) {
Credential.TYPE_PASSWORD_CREDENTIAL ->
- GetPasswordOption.createFrom(from.data)
+ GetPasswordOption.createFrom(from.credentialRetrievalData)
PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL ->
- GetPublicKeyCredentialBaseOption.createFrom(from.data)
+ GetPublicKeyCredentialBaseOption.createFrom(from.credentialRetrievalData)
else ->
- GetCredentialOption(from.type, from.data, from.requireSystemProvider())
+ GetCredentialOption(
+ from.type, from.credentialRetrievalData, from.requireSystemProvider()
+ )
}
} catch (e: FrameworkClassParsingException) {
- GetCredentialOption(from.type, from.data, from.requireSystemProvider())
+ GetCredentialOption(
+ from.type,
+ from.credentialRetrievalData,
+ from.requireSystemProvider()
+ )
}
}
}
diff --git a/packages/InputDevices/res/raw/keyboard_layout_english_us_intl.kcm b/packages/InputDevices/res/raw/keyboard_layout_english_us_intl.kcm
index 66c1c9878fc6..aa31493e9a02 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_english_us_intl.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_english_us_intl.kcm
@@ -292,10 +292,10 @@ key SEMICOLON {
key APOSTROPHE {
label: '\''
- base: '\''
- shift: '"'
- ralt: '\u0301'
- shift+ralt: '\u0308'
+ base: '\u030D'
+ shift: '\u030E'
+ ralt: '\u00B4'
+ shift+ralt: '\u00A8'
}
### ROW 4
diff --git a/packages/SettingsLib/MainSwitchPreference/Android.bp b/packages/SettingsLib/MainSwitchPreference/Android.bp
index dd29827917da..372a276a8967 100644
--- a/packages/SettingsLib/MainSwitchPreference/Android.bp
+++ b/packages/SettingsLib/MainSwitchPreference/Android.bp
@@ -25,5 +25,6 @@ android_library {
"//apex_available:platform",
"com.android.adservices",
"com.android.cellbroadcast",
+ "com.android.healthconnect",
],
}
diff --git a/packages/SettingsLib/Spa/gallery/AndroidManifest.xml b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
index 37d6b426c337..d32d65906efb 100644
--- a/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
+++ b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
@@ -38,7 +38,6 @@
<provider
android:name="com.android.settingslib.spa.search.SpaSearchProvider"
android:authorities="com.android.spa.gallery.search.provider"
- android:enabled="true"
android:exported="false">
</provider>
@@ -67,7 +66,6 @@
<provider
android:name="com.android.settingslib.spa.debug.DebugProvider"
android:authorities="com.android.spa.gallery.debug.provider"
- android:enabled="true"
android:exported="false">
</provider>
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/IllustrationPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/IllustrationPage.kt
index ddf66aae1c22..44f0343e4d2d 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/IllustrationPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/IllustrationPage.kt
@@ -26,9 +26,9 @@ import com.android.settingslib.spa.framework.common.SettingsPageProvider
import com.android.settingslib.spa.framework.compose.navigator
import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.spa.gallery.R
-import com.android.settingslib.spa.widget.Illustration
-import com.android.settingslib.spa.widget.IllustrationModel
-import com.android.settingslib.spa.widget.ResourceType
+import com.android.settingslib.spa.widget.illustration.Illustration
+import com.android.settingslib.spa.widget.illustration.IllustrationModel
+import com.android.settingslib.spa.widget.illustration.ResourceType
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
diff --git a/packages/SettingsLib/Spa/screenshot/Android.bp b/packages/SettingsLib/Spa/screenshot/Android.bp
new file mode 100644
index 000000000000..4e6b646da819
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/Android.bp
@@ -0,0 +1,41 @@
+//
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+ name: "SpaScreenshotTests",
+ test_suites: ["device-tests"],
+
+ asset_dirs: ["assets"],
+
+ srcs: ["src/**/*.kt"],
+
+ certificate: "platform",
+
+ static_libs: [
+ "SpaLib",
+ "SpaLibTestUtils",
+ "androidx.compose.runtime_runtime",
+ "androidx.test.ext.junit",
+ "androidx.test.runner",
+ "mockito-target-minus-junit4",
+ "platform-screenshot-diff-core",
+ ],
+ kotlincflags: ["-Xjvm-default=all"],
+}
diff --git a/packages/SettingsLib/Spa/screenshot/AndroidManifest.xml b/packages/SettingsLib/Spa/screenshot/AndroidManifest.xml
new file mode 100644
index 000000000000..d59a1542498d
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.settingslib.spa.screenshot">
+
+ <uses-sdk android:minSdkVersion="21"/>
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ <activity android:name=".DebugActivity" android:exported="true" />
+ </application>
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:label="Screenshot tests for SpaLib"
+ android:targetPackage="com.android.settingslib.spa.screenshot">
+ </instrumentation>
+</manifest>
diff --git a/packages/SettingsLib/Spa/screenshot/AndroidTest.xml b/packages/SettingsLib/Spa/screenshot/AndroidTest.xml
new file mode 100644
index 000000000000..e0c08e811c46
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/AndroidTest.xml
@@ -0,0 +1,36 @@
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<configuration description="Runs screendiff tests.">
+ <option name="test-suite-tag" value="apct-instrumentation" />
+ <option name="test-suite-tag" value="apct" />
+ <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+ <option name="optimized-property-setting" value="true" />
+ </target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="SpaScreenshotTests.apk" />
+ </target_preparer>
+ <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+ <option name="directory-keys"
+ value="/data/user/0/com.android.settingslib.spa.screenshot/files/settings_screenshots" />
+ <option name="collect-on-run-ended-only" value="true" />
+ </metrics_collector>
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+ <option name="package" value="com.android.settingslib.spa.screenshot" />
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ </test>
+</configuration>
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/dark_landscape_preference.png b/packages/SettingsLib/Spa/screenshot/assets/phone/dark_landscape_preference.png
new file mode 100644
index 000000000000..6086e2d15dd2
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/dark_landscape_preference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/dark_portrait_preference.png b/packages/SettingsLib/Spa/screenshot/assets/phone/dark_portrait_preference.png
new file mode 100644
index 000000000000..aa6c5b7cd131
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/dark_portrait_preference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_preference.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_preference.png
new file mode 100644
index 000000000000..cac990caad63
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_preference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_preference.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_preference.png
new file mode 100644
index 000000000000..f6298c0b62c4
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_preference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_landscape_preference.png b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_landscape_preference.png
new file mode 100644
index 000000000000..9391eeb01e74
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_landscape_preference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_preference.png b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_preference.png
new file mode 100644
index 000000000000..94e28437c04e
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_preference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/tablet/light_landscape_preference.png b/packages/SettingsLib/Spa/screenshot/assets/tablet/light_landscape_preference.png
new file mode 100644
index 000000000000..b1d03c31362e
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/assets/tablet/light_landscape_preference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/tablet/light_portrait_preference.png b/packages/SettingsLib/Spa/screenshot/assets/tablet/light_portrait_preference.png
new file mode 100644
index 000000000000..95f19da3c52c
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/assets/tablet/light_portrait_preference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/Bitmap.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/Bitmap.kt
new file mode 100644
index 000000000000..814d4a1d1ede
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/Bitmap.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.screenshot
+
+import android.graphics.Bitmap
+import android.graphics.Canvas
+import android.os.Build
+import android.view.View
+import platform.test.screenshot.matchers.MSSIMMatcher
+import platform.test.screenshot.matchers.PixelPerfectMatcher
+
+/** Draw this [View] into a [Bitmap]. */
+// TODO(b/195673633): Remove this once Compose screenshot tests use hardware rendering for their
+// tests.
+fun View.drawIntoBitmap(): Bitmap {
+ val bitmap =
+ Bitmap.createBitmap(
+ measuredWidth,
+ measuredHeight,
+ Bitmap.Config.ARGB_8888,
+ )
+ val canvas = Canvas(bitmap)
+ draw(canvas)
+ return bitmap
+}
+
+/**
+ * The [BitmapMatcher][platform.test.screenshot.matchers.BitmapMatcher] that should be used for
+ * screenshot *unit* tests.
+ */
+val UnitTestBitmapMatcher =
+ if (Build.CPU_ABI == "x86_64") {
+ // Different CPU architectures can sometimes end up rendering differently, so we can't do
+ // pixel-perfect matching on different architectures using the same golden. Given that our
+ // presubmits are run on cf_x86_64_phone, our goldens should be perfectly matched on the
+ // x86_64 architecture and use the Structural Similarity Index on others.
+ // TODO(b/237511747): Run our screenshot presubmit tests on arm64 instead so that we can
+ // do pixel perfect matching both at presubmit time and at development time with actual
+ // devices.
+ PixelPerfectMatcher()
+ } else {
+ MSSIMMatcher()
+ }
+
+/**
+ * The [BitmapMatcher][platform.test.screenshot.matchers.BitmapMatcher] that should be used for
+ * screenshot *unit* tests.
+ *
+ * We use the Structural Similarity Index for integration tests because they usually contain
+ * additional information and noise that shouldn't break the test.
+ */
+val IntegrationTestBitmapMatcher = MSSIMMatcher()
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/DefaultDeviceEmulationSpec.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/DefaultDeviceEmulationSpec.kt
new file mode 100644
index 000000000000..d7f42b3d1be3
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/DefaultDeviceEmulationSpec.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.screenshot
+
+import platform.test.screenshot.DeviceEmulationSpec
+import platform.test.screenshot.DisplaySpec
+
+/**
+ * The emulations specs for all 8 permutations of:
+ * - phone or tablet.
+ * - dark of light mode.
+ * - portrait or landscape.
+ */
+val DeviceEmulationSpec.Companion.PhoneAndTabletFull
+ get() = PhoneAndTabletFullSpec
+
+private val PhoneAndTabletFullSpec =
+ DeviceEmulationSpec.forDisplays(Displays.Phone, Displays.Tablet)
+
+/**
+ * The emulations specs of:
+ * - phone + light mode + portrait.
+ * - phone + light mode + landscape.
+ * - tablet + dark mode + portrait.
+ *
+ * This allows to test the most important permutations of a screen/layout with only 3
+ * configurations.
+ */
+val DeviceEmulationSpec.Companion.PhoneAndTabletMinimal
+ get() = PhoneAndTabletMinimalSpec
+
+private val PhoneAndTabletMinimalSpec =
+ DeviceEmulationSpec.forDisplays(Displays.Phone, isDarkTheme = false) +
+ DeviceEmulationSpec.forDisplays(Displays.Tablet, isDarkTheme = true, isLandscape = false)
+
+object Displays {
+ val Phone =
+ DisplaySpec(
+ "phone",
+ width = 1440,
+ height = 3120,
+ densityDpi = 560,
+ )
+
+ val Tablet =
+ DisplaySpec(
+ "tablet",
+ width = 2560,
+ height = 1600,
+ densityDpi = 320,
+ )
+}
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsGoldenImagePathManager.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsGoldenImagePathManager.kt
new file mode 100644
index 000000000000..25bc098b29b7
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsGoldenImagePathManager.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.screenshot
+
+import androidx.test.platform.app.InstrumentationRegistry
+import platform.test.screenshot.GoldenImagePathManager
+import platform.test.screenshot.PathConfig
+
+/** A [GoldenImagePathManager] that should be used for all Settings screenshot tests. */
+class SettingsGoldenImagePathManager(
+ pathConfig: PathConfig,
+ assetsPathRelativeToBuildRoot: String
+) :
+ GoldenImagePathManager(
+ appContext = InstrumentationRegistry.getInstrumentation().context,
+ assetsPathRelativeToBuildRoot = assetsPathRelativeToBuildRoot,
+ deviceLocalPath =
+ InstrumentationRegistry.getInstrumentation()
+ .targetContext
+ .filesDir
+ .absolutePath
+ .toString() + "/settings_screenshots",
+ pathConfig = pathConfig,
+ ) {
+ override fun toString(): String {
+ // This string is appended to all actual/expected screenshots on the device, so make sure
+ // it is a static value.
+ return "SettingsGoldenImagePathManager"
+ }
+}
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsScreenshotTestRule.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsScreenshotTestRule.kt
new file mode 100644
index 000000000000..7a7cf318fb4f
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsScreenshotTestRule.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.screenshot
+
+import androidx.activity.ComponentActivity
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.platform.ViewRootForTest
+import androidx.compose.ui.test.junit4.createAndroidComposeRule
+import androidx.compose.ui.test.onRoot
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+import org.junit.rules.RuleChain
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+import platform.test.screenshot.DeviceEmulationRule
+import platform.test.screenshot.DeviceEmulationSpec
+import platform.test.screenshot.MaterialYouColorsRule
+import platform.test.screenshot.ScreenshotTestRule
+import platform.test.screenshot.getEmulatedDevicePathConfig
+
+/** A rule for Settings screenshot diff tests. */
+class SettingsScreenshotTestRule(
+ emulationSpec: DeviceEmulationSpec,
+ assetsPathRelativeToBuildRoot: String
+) : TestRule {
+ private val colorsRule = MaterialYouColorsRule()
+ private val deviceEmulationRule = DeviceEmulationRule(emulationSpec)
+ private val screenshotRule =
+ ScreenshotTestRule(
+ SettingsGoldenImagePathManager(
+ getEmulatedDevicePathConfig(emulationSpec),
+ assetsPathRelativeToBuildRoot
+ )
+ )
+ private val composeRule = createAndroidComposeRule<ComponentActivity>()
+ private val delegateRule =
+ RuleChain.outerRule(colorsRule)
+ .around(deviceEmulationRule)
+ .around(screenshotRule)
+ .around(composeRule)
+ private val matcher = UnitTestBitmapMatcher
+
+ override fun apply(base: Statement, description: Description): Statement {
+ return delegateRule.apply(base, description)
+ }
+
+ /**
+ * Compare [content] with the golden image identified by [goldenIdentifier] in the context of
+ * [testSpec].
+ */
+ fun screenshotTest(
+ goldenIdentifier: String,
+ content: @Composable () -> Unit,
+ ) {
+ // Make sure that the activity draws full screen and fits the whole display.
+ val activity = composeRule.activity
+ activity.mainExecutor.execute { activity.window.setDecorFitsSystemWindows(false) }
+
+ // Set the content using the AndroidComposeRule to make sure that the Activity is set up
+ // correctly.
+ composeRule.setContent {
+ SettingsTheme {
+ Surface(
+ color = MaterialTheme.colorScheme.background,
+ ) {
+ content()
+ }
+ }
+ }
+ composeRule.waitForIdle()
+
+ val view = (composeRule.onRoot().fetchSemanticsNode().root as ViewRootForTest).view
+ screenshotRule.assertBitmapAgainstGolden(view.drawIntoBitmap(), goldenIdentifier, matcher)
+ }
+}
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/PreferenceScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/PreferenceScreenshotTest.kt
new file mode 100644
index 000000000000..963182638293
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/PreferenceScreenshotTest.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.screenshot
+
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.Autorenew
+import androidx.compose.material.icons.outlined.DisabledByDefault
+import androidx.compose.runtime.Composable
+import com.android.settingslib.spa.framework.compose.toState
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.scaffold.RegularScaffold
+import com.android.settingslib.spa.widget.ui.SettingsIcon
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import platform.test.screenshot.DeviceEmulationSpec
+
+/** A screenshot test for ExampleFeature. */
+@RunWith(Parameterized::class)
+class PreferenceScreenshotTest(emulationSpec: DeviceEmulationSpec) {
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getTestSpecs() = DeviceEmulationSpec.PhoneAndTabletFull
+ private const val TITLE = "Title"
+ private const val SUMMARY = "Summary"
+ private const val LONG_SUMMARY =
+ "Long long long long long long long long long long long long long long long summary"
+ }
+
+ @get:Rule
+ val screenshotRule =
+ SettingsScreenshotTestRule(
+ emulationSpec,
+ "frameworks/base/packages/SettingsLib/Spa/screenshot/assets"
+ )
+
+ @Test
+ fun testPreference() {
+ screenshotRule.screenshotTest("preference") {
+ RegularScaffold(title = "Preference") {
+ Preference(object : PreferenceModel {
+ override val title = TITLE
+ })
+
+ Preference(object : PreferenceModel {
+ override val title = TITLE
+ override val summary = SUMMARY.toState()
+ })
+
+ Preference(object : PreferenceModel {
+ override val title = TITLE
+ override val summary = LONG_SUMMARY.toState()
+ })
+
+ Preference(object : PreferenceModel {
+ override val title = TITLE
+ override val summary = SUMMARY.toState()
+ override val enabled = false.toState()
+ override val icon = @Composable {
+ SettingsIcon(imageVector = Icons.Outlined.DisabledByDefault)
+ }
+ })
+
+ Preference(object : PreferenceModel {
+ override val title = TITLE
+ override val summary = SUMMARY.toState()
+ override val icon = @Composable {
+ SettingsIcon(imageVector = Icons.Outlined.Autorenew)
+ }
+ })
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/packages/SettingsLib/Spa/settings.gradle b/packages/SettingsLib/Spa/settings.gradle
index b627a7036f66..1c5a1ceda34a 100644
--- a/packages/SettingsLib/Spa/settings.gradle
+++ b/packages/SettingsLib/Spa/settings.gradle
@@ -33,4 +33,3 @@ rootProject.name = "SpaLib"
include ':spa'
include ':gallery'
include ':testutils'
-include ':tests'
diff --git a/packages/SettingsLib/Spa/spa/build.gradle b/packages/SettingsLib/Spa/spa/build.gradle
index b1d8d0dadfbd..19963fbeed82 100644
--- a/packages/SettingsLib/Spa/spa/build.gradle
+++ b/packages/SettingsLib/Spa/spa/build.gradle
@@ -27,6 +27,8 @@ android {
defaultConfig {
minSdk MIN_SDK
targetSdk TARGET_SDK
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
sourceSets {
@@ -37,6 +39,13 @@ android {
res.srcDirs = ["res"]
manifest.srcFile "AndroidManifest.xml"
}
+ androidTest {
+ kotlin {
+ srcDir "../tests/src"
+ }
+ res.srcDirs = ["../tests/res"]
+ manifest.srcFile "../tests/AndroidManifest.xml"
+ }
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
@@ -52,6 +61,11 @@ android {
composeOptions {
kotlinCompilerExtensionVersion jetpack_compose_compiler_version
}
+ buildTypes {
+ debug {
+ testCoverageEnabled = true
+ }
+ }
}
dependencies {
@@ -72,4 +86,29 @@ dependencies {
api "com.google.android.material:material:1.7.0-alpha03"
debugApi "androidx.compose.ui:ui-tooling:$jetpack_compose_version"
implementation "com.airbnb.android:lottie-compose:5.2.0"
+
+ androidTestImplementation project(":testutils")
+ androidTestImplementation "com.linkedin.dexmaker:dexmaker-mockito:2.28.1"
+}
+
+task coverageReport(type: JacocoReport, dependsOn: "connectedDebugAndroidTest") {
+ group = "Reporting"
+ description = "Generate Jacoco coverage reports after running tests."
+
+ sourceDirectories.from = files("src")
+ classDirectories.from = fileTree(
+ dir: "$buildDir/tmp/kotlin-classes/debug",
+ excludes: [
+ "com/android/settingslib/spa/debug/**",
+
+ // Excludes files forked from AndroidX.
+ "com/android/settingslib/spa/widget/scaffold/CustomizedAppBar*",
+ "com/android/settingslib/spa/widget/scaffold/TopAppBarColors*",
+
+ // Excludes files forked from Accompanist.
+ "com/android/settingslib/spa/framework/compose/DrawablePainter*",
+ "com/android/settingslib/spa/framework/compose/Pager*",
+ ],
+ )
+ executionData.from = fileTree(dir: "$buildDir/outputs/code_coverage/debugAndroidTest/connected")
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugFormat.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugFormat.kt
index 58736350dc7f..6ecf9c39c9fa 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugFormat.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugFormat.kt
@@ -40,7 +40,7 @@ private fun EntryStatusData.debugContent(): String {
}
fun SettingsPage.debugArguments(): String {
- val normArguments = parameter.normalize(arguments)
+ val normArguments = parameter.normalize(arguments, eraseRuntimeValues = true)
if (normArguments == null || normArguments.isEmpty) return "[No arguments]"
return normArguments.toString().removeRange(0, 6)
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugProvider.kt
index 59ec985ba253..838c0cf7cbad 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugProvider.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugProvider.kt
@@ -27,11 +27,7 @@ import android.database.Cursor
import android.database.MatrixCursor
import android.net.Uri
import android.util.Log
-import com.android.settingslib.spa.framework.common.ColumnEnum
-import com.android.settingslib.spa.framework.common.QueryEnum
import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
-import com.android.settingslib.spa.framework.common.addUri
-import com.android.settingslib.spa.framework.common.getColumns
import com.android.settingslib.spa.framework.util.KEY_DESTINATION
import com.android.settingslib.spa.framework.util.KEY_HIGHLIGHT_ENTRY
import com.android.settingslib.spa.framework.util.KEY_SESSION_SOURCE_NAME
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/ProviderColumn.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/ProviderColumn.kt
index 61b46be443c2..bb9a1345b70b 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/ProviderColumn.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/ProviderColumn.kt
@@ -14,10 +14,9 @@
* limitations under the License.
*/
-package com.android.settingslib.spa.framework.common
+package com.android.settingslib.spa.debug
import android.content.UriMatcher
-import androidx.annotation.VisibleForTesting
/**
* Enum to define all column names in provider.
@@ -39,12 +38,6 @@ enum class ColumnEnum(val id: String) {
ENTRY_INTENT_URI("entryIntent"),
ENTRY_HIERARCHY_PATH("entryPath"),
ENTRY_START_ADB("entryStartAdb"),
-
- // Columns related to search
- SEARCH_TITLE("searchTitle"),
- SEARCH_KEYWORD("searchKw"),
- SEARCH_PATH("searchPath"),
- SEARCH_STATUS_DISABLED("searchDisabled"),
}
/**
@@ -89,54 +82,16 @@ enum class QueryEnum(
ColumnEnum.ENTRY_HIERARCHY_PATH,
)
),
-
- SEARCH_STATIC_DATA_QUERY(
- "search_static", 301,
- listOf(
- ColumnEnum.ENTRY_ID,
- ColumnEnum.ENTRY_INTENT_URI,
- ColumnEnum.SEARCH_TITLE,
- ColumnEnum.SEARCH_KEYWORD,
- ColumnEnum.SEARCH_PATH,
- )
- ),
- SEARCH_DYNAMIC_DATA_QUERY(
- "search_dynamic", 302,
- listOf(
- ColumnEnum.ENTRY_ID,
- ColumnEnum.ENTRY_INTENT_URI,
- ColumnEnum.SEARCH_TITLE,
- ColumnEnum.SEARCH_KEYWORD,
- ColumnEnum.SEARCH_PATH,
- )
- ),
- SEARCH_IMMUTABLE_STATUS_DATA_QUERY(
- "search_immutable_status", 303,
- listOf(
- ColumnEnum.ENTRY_ID,
- ColumnEnum.SEARCH_STATUS_DISABLED,
- )
- ),
- SEARCH_MUTABLE_STATUS_DATA_QUERY(
- "search_mutable_status", 304,
- listOf(
- ColumnEnum.ENTRY_ID,
- ColumnEnum.SEARCH_STATUS_DISABLED,
- )
- ),
}
-@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
-fun QueryEnum.getColumns(): Array<String> {
+internal fun QueryEnum.getColumns(): Array<String> {
return columnNames.map { it.id }.toTypedArray()
}
-@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
-fun QueryEnum.getIndex(name: ColumnEnum): Int {
+internal fun QueryEnum.getIndex(name: ColumnEnum): Int {
return columnNames.indexOf(name)
}
-@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
-fun QueryEnum.addUri(uriMatcher: UriMatcher, authority: String) {
+internal fun QueryEnum.addUri(uriMatcher: UriMatcher, authority: String) {
uriMatcher.addURI(authority, queryPath, queryMatchCode)
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt
index 702c07585be1..4985b44783e9 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt
@@ -35,6 +35,8 @@ interface EntryData {
get() = null
val isHighlighted: Boolean
get() = false
+ val arguments: Bundle?
+ get() = null
}
val LocalEntryDataProvider =
@@ -121,11 +123,11 @@ data class SettingsEntry(
}
private fun fullArgument(runtimeArguments: Bundle? = null): Bundle {
- val arguments = Bundle()
- if (owner.arguments != null) arguments.putAll(owner.arguments)
- // Put runtime args later, which can override page args.
- if (runtimeArguments != null) arguments.putAll(runtimeArguments)
- return arguments
+ return Bundle().apply {
+ if (owner.arguments != null) putAll(owner.arguments)
+ // Put runtime args later, which can override page args.
+ if (runtimeArguments != null) putAll(runtimeArguments)
+ }
}
fun getStatusData(runtimeArguments: Bundle? = null): EntryStatusData? {
@@ -142,19 +144,21 @@ data class SettingsEntry(
@Composable
fun UiLayout(runtimeArguments: Bundle? = null) {
- CompositionLocalProvider(provideLocalEntryData()) {
- uiLayoutImpl(fullArgument(runtimeArguments))
+ val arguments = remember { fullArgument(runtimeArguments) }
+ CompositionLocalProvider(provideLocalEntryData(arguments)) {
+ uiLayoutImpl(arguments)
}
}
@Composable
- fun provideLocalEntryData(): ProvidedValue<EntryData> {
+ fun provideLocalEntryData(arguments: Bundle): ProvidedValue<EntryData> {
val controller = LocalNavController.current
return LocalEntryDataProvider provides remember {
object : EntryData {
override val pageId = containerPage().id
override val entryId = id
override val isHighlighted = controller.highlightEntryId == id
+ override val arguments = arguments
}
}
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
index 7a39b730342c..2bfa2a4375da 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
@@ -69,7 +69,7 @@ data class SettingsPage(
parameter: List<NamedNavArgument> = emptyList(),
arguments: Bundle? = null
): String {
- val normArguments = parameter.normalize(arguments)
+ val normArguments = parameter.normalize(arguments, eraseRuntimeValues = true)
return "$name:${normArguments?.toString()}".toHashId()
}
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/DrawablePainter.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/DrawablePainter.kt
index ae325f8862eb..e3e1220633ae 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/DrawablePainter.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/DrawablePainter.kt
@@ -20,6 +20,7 @@ import android.graphics.drawable.Animatable
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
+import android.os.Build
import android.os.Handler
import android.os.Looper
import android.view.View
@@ -117,13 +118,17 @@ class DrawablePainter(
return true
}
- override fun applyLayoutDirection(layoutDirection: LayoutDirection): Boolean =
- drawable.setLayoutDirection(
- when (layoutDirection) {
- LayoutDirection.Ltr -> View.LAYOUT_DIRECTION_LTR
- LayoutDirection.Rtl -> View.LAYOUT_DIRECTION_RTL
- }
- )
+ override fun applyLayoutDirection(layoutDirection: LayoutDirection): Boolean {
+ if (Build.VERSION.SDK_INT >= 23) {
+ return drawable.setLayoutDirection(
+ when (layoutDirection) {
+ LayoutDirection.Ltr -> View.LAYOUT_DIRECTION_LTR
+ LayoutDirection.Rtl -> View.LAYOUT_DIRECTION_RTL
+ }
+ )
+ }
+ return false
+ }
override val intrinsicSize: Size get() = drawableIntrinsicSize
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/FlowExt.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/FlowExt.kt
deleted file mode 100644
index dbf8836796ef..000000000000
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/FlowExt.kt
+++ /dev/null
@@ -1,189 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settingslib.spa.framework.compose
-
-import android.annotation.SuppressLint
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.State
-import androidx.compose.runtime.produceState
-import androidx.compose.ui.platform.LocalLifecycleOwner
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.LifecycleOwner
-import androidx.lifecycle.repeatOnLifecycle
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.withContext
-import kotlin.coroutines.CoroutineContext
-import kotlin.coroutines.EmptyCoroutineContext
-
-/**
- * *************************************************************************************************
- * This file was forked from AndroidX:
- * lifecycle/lifecycle-runtime-compose/src/main/java/androidx/lifecycle/compose/FlowExt.kt
- * TODO: Replace with AndroidX when it's usable.
- */
-
-/**
- * Collects values from this [StateFlow] and represents its latest value via [State] in a
- * lifecycle-aware manner.
- *
- * The [StateFlow.value] is used as an initial value. Every time there would be new value posted
- * into the [StateFlow] the returned [State] will be updated causing recomposition of every
- * [State.value] usage whenever the [lifecycleOwner]'s lifecycle is at least [minActiveState].
- *
- * This [StateFlow] is collected every time the [lifecycleOwner]'s lifecycle reaches the
- * [minActiveState] Lifecycle state. The collection stops when the [lifecycleOwner]'s lifecycle
- * falls below [minActiveState].
- *
- * @sample androidx.lifecycle.compose.samples.StateFlowCollectAsStateWithLifecycle
- *
- * Warning: [Lifecycle.State.INITIALIZED] is not allowed in this API. Passing it as a
- * parameter will throw an [IllegalArgumentException].
- *
- * @param lifecycleOwner [LifecycleOwner] whose `lifecycle` is used to restart collecting `this`
- * flow.
- * @param minActiveState [Lifecycle.State] in which the upstream flow gets collected. The
- * collection will stop if the lifecycle falls below that state, and will restart if it's in that
- * state again.
- * @param context [CoroutineContext] to use for collecting.
- */
-@SuppressLint("StateFlowValueCalledInComposition")
-@Composable
-fun <T> StateFlow<T>.collectAsStateWithLifecycle(
- lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
- minActiveState: Lifecycle.State = Lifecycle.State.STARTED,
- context: CoroutineContext = EmptyCoroutineContext
-): State<T> = collectAsStateWithLifecycle(
- initialValue = this.value,
- lifecycle = lifecycleOwner.lifecycle,
- minActiveState = minActiveState,
- context = context
-)
-
-/**
- * Collects values from this [StateFlow] and represents its latest value via [State] in a
- * lifecycle-aware manner.
- *
- * The [StateFlow.value] is used as an initial value. Every time there would be new value posted
- * into the [StateFlow] the returned [State] will be updated causing recomposition of every
- * [State.value] usage whenever the [lifecycle] is at least [minActiveState].
- *
- * This [StateFlow] is collected every time [lifecycle] reaches the [minActiveState] Lifecycle
- * state. The collection stops when [lifecycle] falls below [minActiveState].
- *
- * @sample androidx.lifecycle.compose.samples.StateFlowCollectAsStateWithLifecycle
- *
- * Warning: [Lifecycle.State.INITIALIZED] is not allowed in this API. Passing it as a
- * parameter will throw an [IllegalArgumentException].
- *
- * @param lifecycle [Lifecycle] used to restart collecting `this` flow.
- * @param minActiveState [Lifecycle.State] in which the upstream flow gets collected. The
- * collection will stop if the lifecycle falls below that state, and will restart if it's in that
- * state again.
- * @param context [CoroutineContext] to use for collecting.
- */
-@SuppressLint("StateFlowValueCalledInComposition")
-@Composable
-fun <T> StateFlow<T>.collectAsStateWithLifecycle(
- lifecycle: Lifecycle,
- minActiveState: Lifecycle.State = Lifecycle.State.STARTED,
- context: CoroutineContext = EmptyCoroutineContext
-): State<T> = collectAsStateWithLifecycle(
- initialValue = this.value,
- lifecycle = lifecycle,
- minActiveState = minActiveState,
- context = context
-)
-
-/**
- * Collects values from this [Flow] and represents its latest value via [State] in a
- * lifecycle-aware manner.
- *
- * Every time there would be new value posted into the [Flow] the returned [State] will be updated
- * causing recomposition of every [State.value] usage whenever the [lifecycleOwner]'s lifecycle is
- * at least [minActiveState].
- *
- * This [Flow] is collected every time the [lifecycleOwner]'s lifecycle reaches the [minActiveState]
- * Lifecycle state. The collection stops when the [lifecycleOwner]'s lifecycle falls below
- * [minActiveState].
- *
- * @sample androidx.lifecycle.compose.samples.FlowCollectAsStateWithLifecycle
- *
- * Warning: [Lifecycle.State.INITIALIZED] is not allowed in this API. Passing it as a
- * parameter will throw an [IllegalArgumentException].
- *
- * @param initialValue The initial value given to the returned [State.value].
- * @param lifecycleOwner [LifecycleOwner] whose `lifecycle` is used to restart collecting `this`
- * flow.
- * @param minActiveState [Lifecycle.State] in which the upstream flow gets collected. The
- * collection will stop if the lifecycle falls below that state, and will restart if it's in that
- * state again.
- * @param context [CoroutineContext] to use for collecting.
- */
-@Composable
-fun <T> Flow<T>.collectAsStateWithLifecycle(
- initialValue: T,
- lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
- minActiveState: Lifecycle.State = Lifecycle.State.STARTED,
- context: CoroutineContext = EmptyCoroutineContext
-): State<T> = collectAsStateWithLifecycle(
- initialValue = initialValue,
- lifecycle = lifecycleOwner.lifecycle,
- minActiveState = minActiveState,
- context = context
-)
-
-/**
- * Collects values from this [Flow] and represents its latest value via [State] in a
- * lifecycle-aware manner.
- *
- * Every time there would be new value posted into the [Flow] the returned [State] will be updated
- * causing recomposition of every [State.value] usage whenever the [lifecycle] is at
- * least [minActiveState].
- *
- * This [Flow] is collected every time [lifecycle] reaches the [minActiveState] Lifecycle
- * state. The collection stops when [lifecycle] falls below [minActiveState].
- *
- * @sample androidx.lifecycle.compose.samples.FlowCollectAsStateWithLifecycle
- *
- * Warning: [Lifecycle.State.INITIALIZED] is not allowed in this API. Passing it as a
- * parameter will throw an [IllegalArgumentException].
- *
- * @param initialValue The initial value given to the returned [State.value].
- * @param lifecycle [Lifecycle] used to restart collecting `this` flow.
- * @param minActiveState [Lifecycle.State] in which the upstream flow gets collected. The
- * collection will stop if the lifecycle falls below that state, and will restart if it's in that
- * state again.
- * @param context [CoroutineContext] to use for collecting.
- */
-@Composable
-fun <T> Flow<T>.collectAsStateWithLifecycle(
- initialValue: T,
- lifecycle: Lifecycle,
- minActiveState: Lifecycle.State = Lifecycle.State.STARTED,
- context: CoroutineContext = EmptyCoroutineContext
-): State<T> {
- return produceState(initialValue, this, lifecycle, minActiveState, context) {
- lifecycle.repeatOnLifecycle(minActiveState) {
- if (context == EmptyCoroutineContext) {
- this@collectAsStateWithLifecycle.collect { this@produceState.value = it }
- } else withContext(context) {
- this@collectAsStateWithLifecycle.collect { this@produceState.value = it }
- }
- }
- }
-}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/Keyboards.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/Keyboards.kt
index 8d0313fdce9f..3f7cc199aed8 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/Keyboards.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/Keyboards.kt
@@ -18,7 +18,6 @@ package com.android.settingslib.spa.framework.compose
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.rememberLazyListState
-import androidx.compose.foundation.text.KeyboardActionScope
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.snapshotFlow
@@ -32,7 +31,7 @@ import kotlinx.coroutines.flow.filter
*/
@OptIn(ExperimentalComposeUiApi::class)
@Composable
-fun hideKeyboardAction(): KeyboardActionScope.() -> Unit {
+fun hideKeyboardAction(): () -> Unit {
val keyboardController = LocalSoftwareKeyboardController.current
return { keyboardController?.hide() }
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/Pager.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/Pager.kt
index 4df7794e8759..392089ae7989 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/Pager.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/Pager.kt
@@ -19,8 +19,6 @@ package com.android.settingslib.spa.framework.compose
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
-import androidx.compose.foundation.layout.calculateEndPadding
-import androidx.compose.foundation.layout.calculateStartPadding
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyRow
@@ -36,7 +34,6 @@ import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalDensity
-import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.Velocity
import androidx.compose.ui.unit.dp
@@ -123,7 +120,7 @@ fun VerticalPager(
contentPadding: PaddingValues = PaddingValues(0.dp),
horizontalAlignment: Alignment.Horizontal = Alignment.CenterHorizontally,
key: ((page: Int) -> Any)? = null,
- content: @Composable() (PagerScope.(page: Int) -> Unit),
+ content: @Composable PagerScope.(page: Int) -> Unit,
) {
Pager(
count = count,
@@ -175,24 +172,8 @@ internal fun Pager(
.collect { state.updateCurrentPageBasedOnLazyListState() }
}
val density = LocalDensity.current
- val layoutDirection = LocalLayoutDirection.current
- LaunchedEffect(density, contentPadding, isVertical, layoutDirection, reverseLayout, state) {
- with(density) {
- // this should be exposed on LazyListLayoutInfo instead. b/200920410
- state.afterContentPadding = if (isVertical) {
- if (!reverseLayout) {
- contentPadding.calculateBottomPadding()
- } else {
- contentPadding.calculateTopPadding()
- }
- } else {
- if (!reverseLayout) {
- contentPadding.calculateEndPadding(layoutDirection)
- } else {
- contentPadding.calculateStartPadding(layoutDirection)
- }
- }.roundToPx()
- }
+ LaunchedEffect(density, state, itemSpacing) {
+ with(density) { state.itemSpacing = itemSpacing.roundToPx() }
}
val pagerScope = remember(state) { PagerScopeImpl(state) }
@@ -203,6 +184,7 @@ internal fun Pager(
ConsumeFlingNestedScrollConnection(
consumeHorizontal = !isVertical,
consumeVertical = isVertical,
+ pagerState = state,
)
}
@@ -268,6 +250,7 @@ internal fun Pager(
private class ConsumeFlingNestedScrollConnection(
private val consumeHorizontal: Boolean,
private val consumeVertical: Boolean,
+ private val pagerState: PagerState,
) : NestedScrollConnection {
override fun onPostScroll(
consumed: Offset,
@@ -281,9 +264,15 @@ private class ConsumeFlingNestedScrollConnection(
}
override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity {
- // We can consume all post fling velocity on the main-axis
- // so that it doesn't propagate up to the Pager
- return available.consume(consumeHorizontal, consumeVertical)
+ return if (pagerState.currentPageOffset != 0f) {
+ // The Pager is already scrolling. This means that a nested scroll child was
+ // scrolled to end, and the Pager can use this fling
+ Velocity.Zero
+ } else {
+ // A nested scroll child is still scrolling. We can consume all post fling
+ // velocity on the main-axis so that it doesn't propagate up to the Pager
+ available.consume(consumeHorizontal, consumeVertical)
+ }
}
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/PagerState.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/PagerState.kt
index 21ba11739af0..480335dacd36 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/PagerState.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/PagerState.kt
@@ -85,12 +85,14 @@ class PagerState(
return layoutInfo.visibleItemsInfo.maxByOrNull {
val start = maxOf(it.offset, 0)
val end = minOf(
- it.offset + it.size, layoutInfo.viewportEndOffset - afterContentPadding)
+ it.offset + it.size,
+ layoutInfo.viewportEndOffset - layoutInfo.afterContentPadding
+ )
end - start
}
}
- internal var afterContentPadding = 0
+ internal var itemSpacing by mutableStateOf(0)
private val currentPageLayoutInfo: LazyListItemInfo?
get() = lazyListState.layoutInfo.visibleItemsInfo.lastOrNull {
@@ -135,9 +137,7 @@ class PagerState(
*/
val currentPageOffset: Float by derivedStateOf {
currentPageLayoutInfo?.let {
- // We coerce since itemSpacing can make the offset > 1f.
- // We don't want to count spacing in the offset so cap it to 1f
- (-it.offset / it.size.toFloat()).coerceIn(-1f, 1f)
+ (-it.offset / (it.size + itemSpacing).toFloat()).coerceIn(-0.5f, 0.5f)
} ?: 0f
}
@@ -187,28 +187,26 @@ class PagerState(
// offset from the size
lazyListState.animateScrollToItem(
index = page,
- scrollOffset = (target.size * pageOffset).roundToInt()
+ scrollOffset = ((target.size + itemSpacing) * pageOffset).roundToInt()
)
} else if (layoutInfo.visibleItemsInfo.isNotEmpty()) {
// If we don't, we use the current page size as a guide
- val currentSize = layoutInfo.visibleItemsInfo.first().size
+ val currentSize = layoutInfo.visibleItemsInfo.first().size + itemSpacing
lazyListState.animateScrollToItem(
index = page,
scrollOffset = (currentSize * pageOffset).roundToInt()
)
// The target should be visible now
- target = lazyListState.layoutInfo.visibleItemsInfo.firstOrNull {
- it.index == page
- }
+ target = layoutInfo.visibleItemsInfo.firstOrNull { it.index == page }
- if (target != null && target.size != currentSize) {
+ if (target != null && target.size + itemSpacing != currentSize) {
// If the size we used for calculating the offset differs from the actual
// target page size, we need to scroll again. This doesn't look great,
// but there's not much else we can do.
lazyListState.animateScrollToItem(
index = page,
- scrollOffset = (target.size * pageOffset).roundToInt()
+ scrollOffset = ((target.size + itemSpacing) * pageOffset).roundToInt()
)
}
}
@@ -248,7 +246,7 @@ class PagerState(
if (pageOffset.absoluteValue > 0.0001f) {
currentPageLayoutInfo?.let {
scroll {
- scrollBy(it.size * pageOffset)
+ scrollBy((it.size + itemSpacing) * pageOffset)
}
}
}
@@ -295,7 +293,7 @@ class PagerState(
}
private fun requireCurrentPageOffset(value: Float, name: String) {
- require(value in -1f..1f) { "$name must be >= 0 and <= 1" }
+ require(value in -1f..1f) { "$name must be >= -1 and <= 1" }
}
companion object {
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/EntryLogger.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/EntryLogger.kt
index 1c881878f751..8ff436866502 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/EntryLogger.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/EntryLogger.kt
@@ -28,9 +28,12 @@ import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
@Composable
fun logEntryEvent(): (event: LogEvent, extraData: Bundle) -> Unit {
val entryId = LocalEntryDataProvider.current.entryId ?: return { _, _ -> }
+ val arguments = LocalEntryDataProvider.current.arguments
return { event, extraData ->
SpaEnvironmentFactory.instance.logger.event(
- entryId, event, category = LogCategory.VIEW, extraData = extraData
+ entryId, event, category = LogCategory.VIEW, extraData = extraData.apply {
+ if (arguments != null) putAll(arguments)
+ }
)
}
}
@@ -40,7 +43,7 @@ fun wrapOnClickWithLog(onClick: (() -> Unit)?): (() -> Unit)? {
if (onClick == null) return null
val logEvent = logEntryEvent()
return {
- logEvent(LogEvent.ENTRY_CLICK, Bundle.EMPTY)
+ logEvent(LogEvent.ENTRY_CLICK, bundleOf())
onClick()
}
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Flows.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Flows.kt
index 97e3ac2147ca..8bfcff811dd6 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Flows.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Flows.kt
@@ -16,15 +16,10 @@
package com.android.settingslib.spa.framework.util
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.State
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.distinctUntilChangedBy
-import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.take
/**
* Returns a [Flow] whose values are a list which containing the results of applying the given
@@ -41,33 +36,13 @@ inline fun <T, R> Flow<List<T>>.asyncMapItem(crossinline transform: (T) -> R): F
map { list -> list.asyncMap(transform) }
/**
- * Delays the flow a little bit, wait the other flow's first value.
+ * Returns a [Flow] whose values are a list containing only elements matching the given [predicate].
*/
-fun <T1, T2> Flow<T1>.waitFirst(otherFlow: Flow<T2>): Flow<T1> =
- combine(otherFlow.distinctUntilChangedBy {}) { value, _ -> value }
+inline fun <T> Flow<List<T>>.filterItem(crossinline predicate: (T) -> Boolean): Flow<List<T>> =
+ map { list -> list.filter(predicate) }
/**
- * Returns a [Flow] whose values are generated list by combining the most recently emitted non null
- * values by each flow.
+ * Delays the flow a little bit, wait the other flow's first value.
*/
-inline fun <reified T : Any> combineToList(vararg flows: Flow<T?>): Flow<List<T>> = combine(
- flows.asList(),
-) { array: Array<T?> -> array.filterNotNull() }
-
-class StateFlowBridge<T> {
- private val stateFlow = MutableStateFlow<T?>(null)
- val flow = stateFlow.filterNotNull()
-
- fun setIfAbsent(value: T) {
- if (stateFlow.value == null) {
- stateFlow.value = value
- }
- }
-
- @Composable
- fun Sync(state: State<T>) {
- LaunchedEffect(state.value) {
- stateFlow.value = state.value
- }
- }
-}
+fun <T1, T2> Flow<T1>.waitFirst(otherFlow: Flow<T2>): Flow<T1> =
+ combine(otherFlow.take(1)) { value, _ -> value }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/MessageFormats.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/MessageFormats.kt
new file mode 100644
index 000000000000..2adfcca0bf70
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/MessageFormats.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.framework.util
+
+import android.content.Context
+import android.content.res.Resources
+import android.icu.text.MessageFormat
+import android.os.Build
+import androidx.annotation.RequiresApi
+import androidx.annotation.StringRes
+import java.util.Locale
+
+@RequiresApi(Build.VERSION_CODES.N)
+fun Context.formatString(@StringRes resId: Int, vararg arguments: Pair<String, Any>): String =
+ resources.formatString(resId, *arguments)
+
+@RequiresApi(Build.VERSION_CODES.N)
+fun Resources.formatString(@StringRes resId: Int, vararg arguments: Pair<String, Any>): String =
+ MessageFormat(getString(resId), Locale.getDefault(Locale.Category.FORMAT))
+ .format(mapOf(*arguments))
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/PageLogger.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/PageLogger.kt
index a88125472b52..271443e86dac 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/PageLogger.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/PageLogger.kt
@@ -48,7 +48,10 @@ internal fun SettingsPageProvider.PageEvent(arguments: Bundle? = null) {
extraData = bundleOf(
LOG_DATA_DISPLAY_NAME to page.displayName,
LOG_DATA_SESSION_NAME to navController.sessionSourceName,
- )
+ ).apply {
+ val normArguments = parameter.normalize(arguments)
+ if (normArguments != null) putAll(normArguments)
+ }
)
}
if (event == Lifecycle.Event.ON_START) {
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Parameter.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Parameter.kt
index f10d3b097fc8..be303f09fb75 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Parameter.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Parameter.kt
@@ -47,12 +47,15 @@ fun List<NamedNavArgument>.navLink(arguments: Bundle? = null): String {
return argsArray.joinToString("") { arg -> "/$arg" }
}
-fun List<NamedNavArgument>.normalize(arguments: Bundle? = null): Bundle? {
+fun List<NamedNavArgument>.normalize(
+ arguments: Bundle? = null,
+ eraseRuntimeValues: Boolean = false
+): Bundle? {
if (this.isEmpty()) return null
val normArgs = Bundle()
for (navArg in this) {
// Erase value of runtime parameters.
- if (navArg.isRuntimeParam()) {
+ if (navArg.isRuntimeParam() && eraseRuntimeValues) {
normArgs.putString(navArg.name, null)
continue
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/StateFlowBridge.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/StateFlowBridge.kt
new file mode 100644
index 000000000000..494e69b657ca
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/StateFlowBridge.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.framework.util
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.State
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.filterNotNull
+
+/** A StateFlow holder which value could be set or sync from [State]. */
+class StateFlowBridge<T> {
+ private val stateFlow = MutableStateFlow<T?>(null)
+ val flow = stateFlow.filterNotNull()
+
+ fun setIfAbsent(value: T) {
+ if (stateFlow.value == null) {
+ stateFlow.value = value
+ }
+ }
+
+ @Composable
+ fun Sync(state: State<T>) {
+ LaunchedEffect(state.value) {
+ stateFlow.value = state.value
+ }
+ }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/search/SpaSearchContract.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/search/SpaSearchContract.kt
new file mode 100644
index 000000000000..2301f0485040
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/search/SpaSearchContract.kt
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settingslib.spa.search
+
+/**
+ * Intent action used to identify SpaSearchProvider instances. This is used in the {@code
+ * <intent-filter>} of a {@code <provider>}.
+ */
+const val PROVIDER_INTERFACE = "android.content.action.SPA_SEARCH_PROVIDER"
+
+/** ContentProvider path for search static data */
+const val SEARCH_STATIC_DATA = "search_static_data"
+
+/** ContentProvider path for search dynamic data */
+const val SEARCH_DYNAMIC_DATA = "search_dynamic_data"
+
+/** ContentProvider path for search immutable status */
+const val SEARCH_IMMUTABLE_STATUS = "search_immutable_status"
+
+/** ContentProvider path for search mutable status */
+const val SEARCH_MUTABLE_STATUS = "search_mutable_status"
+
+/** Enum to define all column names in provider. */
+enum class ColumnEnum(val id: String) {
+ ENTRY_ID("entryId"),
+ SEARCH_TITLE("searchTitle"),
+ SEARCH_KEYWORD("searchKw"),
+ SEARCH_PATH("searchPath"),
+ INTENT_TARGET_PACKAGE("intentTargetPackage"),
+ INTENT_TARGET_CLASS("intentTargetClass"),
+ INTENT_EXTRAS("intentExtras"),
+ SLICE_URI("sliceUri"),
+ LEGACY_KEY("legacyKey"),
+ ENTRY_DISABLED("entryDisabled"),
+}
+
+/** Enum to define all queries supported in the provider. */
+@SuppressWarnings("Immutable")
+enum class QueryEnum(
+ val queryPath: String,
+ val columnNames: List<ColumnEnum>
+) {
+ SEARCH_STATIC_DATA_QUERY(
+ SEARCH_STATIC_DATA,
+ listOf(
+ ColumnEnum.ENTRY_ID,
+ ColumnEnum.SEARCH_TITLE,
+ ColumnEnum.SEARCH_KEYWORD,
+ ColumnEnum.SEARCH_PATH,
+ ColumnEnum.INTENT_TARGET_PACKAGE,
+ ColumnEnum.INTENT_TARGET_CLASS,
+ ColumnEnum.INTENT_EXTRAS,
+ ColumnEnum.SLICE_URI,
+ ColumnEnum.LEGACY_KEY
+ )
+ ),
+ SEARCH_DYNAMIC_DATA_QUERY(
+ SEARCH_DYNAMIC_DATA,
+ listOf(
+ ColumnEnum.ENTRY_ID,
+ ColumnEnum.SEARCH_TITLE,
+ ColumnEnum.SEARCH_KEYWORD,
+ ColumnEnum.SEARCH_PATH,
+ ColumnEnum.INTENT_TARGET_PACKAGE,
+ ColumnEnum.INTENT_TARGET_CLASS,
+ ColumnEnum.SLICE_URI,
+ ColumnEnum.LEGACY_KEY
+ )
+ ),
+ SEARCH_IMMUTABLE_STATUS_DATA_QUERY(
+ SEARCH_IMMUTABLE_STATUS,
+ listOf(
+ ColumnEnum.ENTRY_ID,
+ ColumnEnum.ENTRY_DISABLED,
+ )
+ ),
+ SEARCH_MUTABLE_STATUS_DATA_QUERY(
+ SEARCH_MUTABLE_STATUS,
+ listOf(
+ ColumnEnum.ENTRY_ID,
+ ColumnEnum.ENTRY_DISABLED,
+ )
+ ),
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/search/SpaSearchProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/search/SpaSearchProvider.kt
index 02aed1ca3399..21bc75a11942 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/search/SpaSearchProvider.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/search/SpaSearchProvider.kt
@@ -19,22 +19,22 @@ package com.android.settingslib.spa.search
import android.content.ContentProvider
import android.content.ContentValues
import android.content.Context
-import android.content.Intent
import android.content.UriMatcher
import android.content.pm.ProviderInfo
import android.database.Cursor
import android.database.MatrixCursor
import android.net.Uri
+import android.os.Parcel
+import android.os.Parcelable
import android.util.Log
import androidx.annotation.VisibleForTesting
-import com.android.settingslib.spa.framework.common.ColumnEnum
-import com.android.settingslib.spa.framework.common.QueryEnum
+import com.android.settingslib.spa.framework.common.EntryStatusData
import com.android.settingslib.spa.framework.common.SettingsEntry
import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
-import com.android.settingslib.spa.framework.common.addUri
-import com.android.settingslib.spa.framework.common.getColumns
import com.android.settingslib.spa.framework.util.SESSION_SEARCH
import com.android.settingslib.spa.framework.util.createIntent
+import com.android.settingslib.spa.slice.fromEntry
+
private const val TAG = "SpaSearchProvider"
@@ -42,18 +42,25 @@ private const val TAG = "SpaSearchProvider"
* The content provider to return entry related data, which can be used for search and hierarchy.
* One can query the provider result by:
* $ adb shell content query --uri content://<AuthorityPath>/<QueryPath>
- * For gallery, AuthorityPath = com.android.spa.gallery.provider
- * For Settings, AuthorityPath = com.android.settings.spa.provider
+ * For gallery, AuthorityPath = com.android.spa.gallery.search.provider
+ * For Settings, AuthorityPath = com.android.settings.spa.search.provider"
* Some examples:
- * $ adb shell content query --uri content://<AuthorityPath>/search_static
- * $ adb shell content query --uri content://<AuthorityPath>/search_dynamic
- * $ adb shell content query --uri content://<AuthorityPath>/search_mutable_status
+ * $ adb shell content query --uri content://<AuthorityPath>/search_static_data
+ * $ adb shell content query --uri content://<AuthorityPath>/search_dynamic_data
* $ adb shell content query --uri content://<AuthorityPath>/search_immutable_status
+ * $ adb shell content query --uri content://<AuthorityPath>/search_mutable_status
*/
class SpaSearchProvider : ContentProvider() {
private val spaEnvironment get() = SpaEnvironmentFactory.instance
private val uriMatcher = UriMatcher(UriMatcher.NO_MATCH)
+ private val queryMatchCode = mapOf(
+ SEARCH_STATIC_DATA to 301,
+ SEARCH_DYNAMIC_DATA to 302,
+ SEARCH_MUTABLE_STATUS to 303,
+ SEARCH_IMMUTABLE_STATUS to 304
+ )
+
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int {
TODO("Implement this to handle requests to delete one or more rows")
}
@@ -85,10 +92,9 @@ class SpaSearchProvider : ContentProvider() {
override fun attachInfo(context: Context?, info: ProviderInfo?) {
if (info != null) {
- QueryEnum.SEARCH_STATIC_DATA_QUERY.addUri(uriMatcher, info.authority)
- QueryEnum.SEARCH_DYNAMIC_DATA_QUERY.addUri(uriMatcher, info.authority)
- QueryEnum.SEARCH_MUTABLE_STATUS_DATA_QUERY.addUri(uriMatcher, info.authority)
- QueryEnum.SEARCH_IMMUTABLE_STATUS_DATA_QUERY.addUri(uriMatcher, info.authority)
+ for (entry in queryMatchCode) {
+ uriMatcher.addURI(info.authority, entry.key, entry.value)
+ }
}
super.attachInfo(context, info)
}
@@ -102,11 +108,11 @@ class SpaSearchProvider : ContentProvider() {
): Cursor? {
return try {
when (uriMatcher.match(uri)) {
- QueryEnum.SEARCH_STATIC_DATA_QUERY.queryMatchCode -> querySearchStaticData()
- QueryEnum.SEARCH_DYNAMIC_DATA_QUERY.queryMatchCode -> querySearchDynamicData()
- QueryEnum.SEARCH_MUTABLE_STATUS_DATA_QUERY.queryMatchCode ->
+ queryMatchCode[SEARCH_STATIC_DATA] -> querySearchStaticData()
+ queryMatchCode[SEARCH_DYNAMIC_DATA] -> querySearchDynamicData()
+ queryMatchCode[SEARCH_MUTABLE_STATUS] ->
querySearchMutableStatusData()
- QueryEnum.SEARCH_IMMUTABLE_STATUS_DATA_QUERY.queryMatchCode ->
+ queryMatchCode[SEARCH_IMMUTABLE_STATUS] ->
querySearchImmutableStatusData()
else -> throw UnsupportedOperationException("Unknown Uri $uri")
}
@@ -167,23 +173,45 @@ class SpaSearchProvider : ContentProvider() {
// Fetch search data. We can add runtime arguments later if necessary
val searchData = entry.getSearchData() ?: return
- val intent = entry.createIntent(SESSION_SEARCH) ?: Intent()
- cursor.newRow()
- .add(ColumnEnum.ENTRY_ID.id, entry.id)
- .add(ColumnEnum.ENTRY_INTENT_URI.id, intent.toUri(Intent.URI_INTENT_SCHEME))
+ val intent = entry.createIntent(SESSION_SEARCH)
+ val row = cursor.newRow().add(ColumnEnum.ENTRY_ID.id, entry.id)
.add(ColumnEnum.SEARCH_TITLE.id, searchData.title)
.add(ColumnEnum.SEARCH_KEYWORD.id, searchData.keyword)
.add(
ColumnEnum.SEARCH_PATH.id,
entryRepository.getEntryPathWithTitle(entry.id, searchData.title)
)
+ intent?.let {
+ row.add(ColumnEnum.INTENT_TARGET_PACKAGE.id, spaEnvironment.appContext.packageName)
+ .add(ColumnEnum.INTENT_TARGET_CLASS.id, spaEnvironment.browseActivityClass?.name)
+ .add(ColumnEnum.INTENT_EXTRAS.id, marshall(intent.extras))
+ }
+ if (entry.hasSliceSupport)
+ row.add(
+ ColumnEnum.SLICE_URI.id, Uri.Builder()
+ .fromEntry(entry, spaEnvironment.sliceProviderAuthorities)
+ )
+ // TODO: support legacy key
}
private fun fetchStatusData(entry: SettingsEntry, cursor: MatrixCursor) {
// Fetch status data. We can add runtime arguments later if necessary
- val statusData = entry.getStatusData() ?: return
+ val statusData = entry.getStatusData() ?: EntryStatusData()
cursor.newRow()
.add(ColumnEnum.ENTRY_ID.id, entry.id)
- .add(ColumnEnum.SEARCH_STATUS_DISABLED.id, statusData.isDisabled)
+ .add(ColumnEnum.ENTRY_DISABLED.id, statusData.isDisabled)
+ }
+
+ private fun QueryEnum.getColumns(): Array<String> {
+ return columnNames.map { it.id }.toTypedArray()
+ }
+
+ private fun marshall(parcelable: Parcelable?): ByteArray? {
+ if (parcelable == null) return null
+ val parcel = Parcel.obtain()
+ parcelable.writeToParcel(parcel, 0)
+ val bytes = parcel.marshall()
+ parcel.recycle()
+ return bytes
}
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/provider/Demo.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/provider/Demo.kt
index b65b91f7990e..3d7d5659923c 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/provider/Demo.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/provider/Demo.kt
@@ -24,6 +24,7 @@ import androidx.slice.Slice
import androidx.slice.SliceManager
import androidx.slice.builders.ListBuilder
import androidx.slice.builders.SliceAction
+import androidx.slice.core.R
import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
import com.android.settingslib.spa.slice.createBroadcastPendingIntent
import com.android.settingslib.spa.slice.createBrowsePendingIntent
@@ -52,10 +53,7 @@ fun createDemoSlice(sliceUri: Uri, title: String, summary: String, intent: Pendi
private fun createSliceAction(context: Context, intent: PendingIntent): SliceAction {
return SliceAction.create(
intent,
- IconCompat.createWithResource(
- context,
- com.google.android.material.R.drawable.navigation_empty_icon
- ),
+ IconCompat.createWithResource(context, R.drawable.notification_action_background),
ListBuilder.ICON_IMAGE,
"Enter app"
)
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/Illustration.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/illustration/Illustration.kt
index cd8a02a10b3d..7cc9bf7c9d50 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/Illustration.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/illustration/Illustration.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.settingslib.spa.widget
+package com.android.settingslib.spa.widget.illustration
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt
index 7db1ca1384f9..ae88ed7c99b9 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt
@@ -132,7 +132,10 @@ private fun Title(title: String, maxLines: Int = Int.MAX_VALUE) {
.asPaddingValues()
.horizontalValues()
)
- .padding(horizontal = SettingsDimension.itemPaddingAround),
+ .padding(
+ start = SettingsDimension.itemPaddingAround,
+ end = SettingsDimension.itemPaddingEnd,
+ ),
overflow = TextOverflow.Ellipsis,
maxLines = maxLines,
)
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt
index b4852e43adce..4218489b4795 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt
@@ -158,6 +158,7 @@ private fun SearchTopAppBar(
private fun SearchBox(query: TextFieldValue, onQueryChange: (TextFieldValue) -> Unit) {
val focusRequester = remember { FocusRequester() }
val textStyle = MaterialTheme.typography.bodyLarge
+ val hideKeyboardAction = hideKeyboardAction()
TextField(
value = query,
onValueChange = onQueryChange,
@@ -173,7 +174,7 @@ private fun SearchBox(query: TextFieldValue, onQueryChange: (TextFieldValue) ->
)
},
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search),
- keyboardActions = KeyboardActions(onSearch = hideKeyboardAction()),
+ keyboardActions = KeyboardActions(onSearch = { hideKeyboardAction() }),
singleLine = true,
colors = TextFieldDefaults.textFieldColors(
containerColor = Color.Transparent,
diff --git a/packages/SettingsLib/Spa/tests/AndroidManifest.xml b/packages/SettingsLib/Spa/tests/AndroidManifest.xml
index e2db5943ae53..1fda4e0bd24e 100644
--- a/packages/SettingsLib/Spa/tests/AndroidManifest.xml
+++ b/packages/SettingsLib/Spa/tests/AndroidManifest.xml
@@ -15,7 +15,7 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.settingslib.spa.tests">
+ package="com.android.settingslib.spa.test">
<uses-sdk android:minSdkVersion="21"/>
@@ -26,6 +26,6 @@
<instrumentation
android:name="androidx.test.runner.AndroidJUnitRunner"
android:label="Tests for SpaLib"
- android:targetPackage="com.android.settingslib.spa.tests">
+ android:targetPackage="com.android.settingslib.spa.test">
</instrumentation>
</manifest>
diff --git a/packages/SettingsLib/Spa/tests/build.gradle b/packages/SettingsLib/Spa/tests/build.gradle
deleted file mode 100644
index 59718951e381..000000000000
--- a/packages/SettingsLib/Spa/tests/build.gradle
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-plugins {
- id 'com.android.library'
- id 'kotlin-android'
-}
-
-android {
- namespace 'com.android.settingslib.spa.tests'
- compileSdk TARGET_SDK
- buildToolsVersion = BUILD_TOOLS_VERSION
-
- defaultConfig {
- minSdk MIN_SDK
- targetSdk TARGET_SDK
-
- testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
- }
-
- sourceSets {
- main {
- res.srcDirs = ["res"]
- }
- androidTest {
- kotlin {
- srcDir "src"
- }
- manifest.srcFile "AndroidManifest.xml"
- }
- }
- compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_8
- targetCompatibility JavaVersion.VERSION_1_8
- }
- kotlinOptions {
- jvmTarget = '1.8'
- freeCompilerArgs = ["-Xjvm-default=all"]
- }
- buildFeatures {
- compose true
- }
- composeOptions {
- kotlinCompilerExtensionVersion jetpack_compose_compiler_version
- }
-}
-
-dependencies {
- androidTestImplementation project(":spa")
- androidTestImplementation project(":testutils")
- androidTestImplementation "com.linkedin.dexmaker:dexmaker-mockito:2.28.1"
-}
diff --git a/packages/SettingsLib/Spa/tests/res/values/strings.xml b/packages/SettingsLib/Spa/tests/res/values/strings.xml
new file mode 100644
index 000000000000..1ca425c26f0a
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/res/values/strings.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources>
+ <string name="test_quantity_strings">{count, plural,
+ =1 {There is one song found.}
+ other {There are # songs found.}
+ }</string>
+
+ <string name="test_quantity_strings_with_param">{count, plural,
+ =1 {There is one song found in {place}.}
+ other {There are # songs found in {place}.}
+ }</string>
+</resources>
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/compose/KeyboardsTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/compose/KeyboardsTest.kt
new file mode 100644
index 000000000000..944ef7fa33fb
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/compose/KeyboardsTest.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.framework.compose
+
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.material3.Text
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalSoftwareKeyboardController
+import androidx.compose.ui.platform.SoftwareKeyboardController
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+
+@OptIn(ExperimentalComposeUiApi::class)
+@RunWith(AndroidJUnit4::class)
+class KeyboardsTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ @get:Rule
+ val mockito: MockitoRule = MockitoJUnit.rule()
+
+ @Mock
+ lateinit var keyboardController: SoftwareKeyboardController
+
+ @Test
+ fun hideKeyboardAction_callControllerHide() {
+ lateinit var action: () -> Unit
+ composeTestRule.setContent {
+ CompositionLocalProvider(LocalSoftwareKeyboardController provides keyboardController) {
+ action = hideKeyboardAction()
+ }
+ }
+
+ action()
+
+ verify(keyboardController).hide()
+ }
+
+ @Test
+ fun rememberLazyListStateAndHideKeyboardWhenStartScroll_notCallHideInitially() {
+ setLazyColumn(scroll = false)
+
+ verify(keyboardController, never()).hide()
+ }
+
+ @Test
+ fun rememberLazyListStateAndHideKeyboardWhenStartScroll_callHideWhenScroll() {
+ setLazyColumn(scroll = true)
+
+ verify(keyboardController).hide()
+ }
+
+ private fun setLazyColumn(scroll: Boolean) {
+ composeTestRule.setContent {
+ CompositionLocalProvider(LocalSoftwareKeyboardController provides keyboardController) {
+ val lazyListState = rememberLazyListStateAndHideKeyboardWhenStartScroll()
+ LazyColumn(
+ modifier = Modifier.size(100.dp),
+ state = lazyListState,
+ ) {
+ items(count = 10) {
+ Text(text = it.toString())
+ }
+ }
+ if (scroll) {
+ LaunchedEffect(Unit) {
+ lazyListState.animateScrollToItem(1)
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/FlowsTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/FlowsTest.kt
new file mode 100644
index 000000000000..4dcdea96ac9d
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/FlowsTest.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.framework.util
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.count
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.toList
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidJUnit4::class)
+class FlowsTest {
+ @Test
+ fun mapItem() = runTest {
+ val inputFlow = flowOf(listOf("A", "BB", "CCC"))
+
+ val outputFlow = inputFlow.mapItem { it.length }
+
+ assertThat(outputFlow.first()).containsExactly(1, 2, 3).inOrder()
+ }
+
+ @Test
+ fun asyncMapItem() = runTest {
+ val inputFlow = flowOf(listOf("A", "BB", "CCC"))
+
+ val outputFlow = inputFlow.asyncMapItem { it.length }
+
+ assertThat(outputFlow.first()).containsExactly(1, 2, 3).inOrder()
+ }
+
+ @Test
+ fun filterItem() = runTest {
+ val inputFlow = flowOf(listOf("A", "BB", "CCC"))
+
+ val outputFlow = inputFlow.filterItem { it.length >= 2 }
+
+ assertThat(outputFlow.first()).containsExactly("BB", "CCC").inOrder()
+ }
+
+ @Test
+ fun waitFirst_otherFlowEmpty() = runTest {
+ val mainFlow = flowOf("A")
+ val otherFlow = emptyFlow<String>()
+
+ val outputFlow = mainFlow.waitFirst(otherFlow)
+
+ assertThat(outputFlow.count()).isEqualTo(0)
+ }
+
+ @Test
+ fun waitFirst_otherFlowOneValue() = runTest {
+ val mainFlow = flowOf("A")
+ val otherFlow = flowOf("B")
+
+ val outputFlow = mainFlow.waitFirst(otherFlow)
+
+ assertThat(outputFlow.toList()).containsExactly("A")
+ }
+
+ @Test
+ fun waitFirst_otherFlowTwoValues() = runTest {
+ val mainFlow = flowOf("A")
+ val otherFlow = flowOf("B", "B")
+
+ val outputFlow = mainFlow.waitFirst(otherFlow)
+
+ assertThat(outputFlow.toList()).containsExactly("A")
+ }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/MessageFormatsTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/MessageFormatsTest.kt
new file mode 100644
index 000000000000..2017ad1df517
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/MessageFormatsTest.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.framework.util
+
+import android.content.Context
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.test.R
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class MessageFormatsTest {
+ private val context: Context = ApplicationProvider.getApplicationContext()
+
+ @Test
+ fun formatString_one() {
+ val message = context.formatString(R.string.test_quantity_strings, "count" to 1)
+
+ assertThat(message).isEqualTo("There is one song found.")
+ }
+
+ @Test
+ fun formatString_other() {
+ val message = context.formatString(R.string.test_quantity_strings, "count" to 2)
+
+ assertThat(message).isEqualTo("There are 2 songs found.")
+ }
+
+ @Test
+ fun formatString_withParam_one() {
+ val message = context.formatString(
+ R.string.test_quantity_strings_with_param,
+ "count" to 1,
+ "place" to "phone",
+ )
+
+ assertThat(message).isEqualTo("There is one song found in phone.")
+ }
+
+ @Test
+ fun formatString_withParam_other() {
+ val message = context.formatString(
+ R.string.test_quantity_strings_with_param,
+ "count" to 2,
+ "place" to "phone",
+ )
+
+ assertThat(message).isEqualTo("There are 2 songs found in phone.")
+ }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/ParameterTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/ParameterTest.kt
index 48ebd8d8ade8..0aa846c6f997 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/ParameterTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/ParameterTest.kt
@@ -88,7 +88,7 @@ class ParameterTest {
val emptyParam = navArguments.normalize()
assertThat(emptyParam).isNotNull()
assertThat(emptyParam.toString()).isEqualTo(
- "Bundle[{rt_param=null, unset_string_param=null, unset_int_param=null}]"
+ "Bundle[{unset_rt_param=null, unset_string_param=null, unset_int_param=null}]"
)
val setPartialParam = navArguments.normalize(
@@ -99,7 +99,7 @@ class ParameterTest {
)
assertThat(setPartialParam).isNotNull()
assertThat(setPartialParam.toString()).isEqualTo(
- "Bundle[{rt_param=null, string_param=myStr, unset_int_param=null}]"
+ "Bundle[{rt_param=rtStr, string_param=myStr, unset_int_param=null}]"
)
val setAllParam = navArguments.normalize(
@@ -107,7 +107,8 @@ class ParameterTest {
"string_param" to "myStr",
"int_param" to 10,
"rt_param" to "rtStr",
- )
+ ),
+ eraseRuntimeValues = true
)
assertThat(setAllParam).isNotNull()
assertThat(setAllParam.toString()).isEqualTo(
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/StateFlowBridgeTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/StateFlowBridgeTest.kt
new file mode 100644
index 000000000000..e1d9a28a9045
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/StateFlowBridgeTest.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.framework.util
+
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.framework.compose.stateOf
+import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidJUnit4::class)
+class StateFlowBridgeTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ @Test
+ fun stateFlowBridge_initial() = runTest {
+ val stateFlowBridge = StateFlowBridge<String>()
+
+ val flow = stateFlowBridge.flow
+
+ val first = flow.firstWithTimeoutOrNull()
+ assertThat(first).isNull()
+ }
+
+ @Test
+ fun stateFlowBridge_setIfAbsent() = runTest {
+ val stateFlowBridge = StateFlowBridge<String>()
+
+ stateFlowBridge.setIfAbsent("A")
+
+ val first = stateFlowBridge.flow.firstWithTimeoutOrNull()
+ assertThat(first).isEqualTo("A")
+ }
+
+ @Test
+ fun stateFlowBridge_sync() = runTest {
+ val stateFlowBridge = StateFlowBridge<String>()
+
+ composeTestRule.setContent {
+ stateFlowBridge.Sync(stateOf("A"))
+ }
+
+ val first = stateFlowBridge.flow.firstWithTimeoutOrNull()
+ assertThat(first).isEqualTo("A")
+ }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/search/SpaSearchProviderTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/search/SpaSearchProviderTest.kt
index cdb0f3a2283d..831aded638a2 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/search/SpaSearchProviderTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/search/SpaSearchProviderTest.kt
@@ -18,15 +18,14 @@ package com.android.settingslib.spa.search
import android.content.Context
import android.database.Cursor
+import android.os.Bundle
+import android.os.Parcel
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.android.settingslib.spa.framework.common.ColumnEnum
-import com.android.settingslib.spa.framework.common.QueryEnum
import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
import com.android.settingslib.spa.framework.common.SettingsPage
import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
import com.android.settingslib.spa.framework.common.createSettingsPage
-import com.android.settingslib.spa.framework.common.getIndex
import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest
import com.android.settingslib.spa.tests.testutils.SppForSearch
import com.google.common.truth.Truth
@@ -39,51 +38,145 @@ class SpaSearchProviderTest {
private val spaEnvironment =
SpaEnvironmentForTest(context, listOf(SppForSearch.createSettingsPage()))
private val searchProvider = SpaSearchProvider()
+ private val pageOwner = spaEnvironment.createPage("SppForSearch")
@Test
fun testQuerySearchStatusData() {
SpaEnvironmentFactory.reset(spaEnvironment)
- val pageOwner = spaEnvironment.createPage("SppForSearch")
val immutableStatus = searchProvider.querySearchImmutableStatusData()
- Truth.assertThat(immutableStatus.count).isEqualTo(1)
+ Truth.assertThat(immutableStatus.count).isEqualTo(2)
immutableStatus.moveToFirst()
immutableStatus.checkValue(
QueryEnum.SEARCH_IMMUTABLE_STATUS_DATA_QUERY,
ColumnEnum.ENTRY_ID,
+ pageOwner.getEntryId("SearchStaticWithNoStatus")
+ )
+ immutableStatus.checkValue(
+ QueryEnum.SEARCH_IMMUTABLE_STATUS_DATA_QUERY,
+ ColumnEnum.ENTRY_DISABLED,
+ false.toString()
+ )
+
+ immutableStatus.moveToNext()
+ immutableStatus.checkValue(
+ QueryEnum.SEARCH_IMMUTABLE_STATUS_DATA_QUERY,
+ ColumnEnum.ENTRY_ID,
pageOwner.getEntryId("SearchDynamicWithImmutableStatus")
)
+ immutableStatus.checkValue(
+ QueryEnum.SEARCH_IMMUTABLE_STATUS_DATA_QUERY, ColumnEnum.ENTRY_DISABLED, true.toString()
+ )
val mutableStatus = searchProvider.querySearchMutableStatusData()
Truth.assertThat(mutableStatus.count).isEqualTo(2)
mutableStatus.moveToFirst()
mutableStatus.checkValue(
- QueryEnum.SEARCH_IMMUTABLE_STATUS_DATA_QUERY,
+ QueryEnum.SEARCH_MUTABLE_STATUS_DATA_QUERY,
ColumnEnum.ENTRY_ID,
pageOwner.getEntryId("SearchStaticWithMutableStatus")
)
+ mutableStatus.checkValue(
+ QueryEnum.SEARCH_MUTABLE_STATUS_DATA_QUERY, ColumnEnum.ENTRY_DISABLED, false.toString()
+ )
mutableStatus.moveToNext()
mutableStatus.checkValue(
- QueryEnum.SEARCH_IMMUTABLE_STATUS_DATA_QUERY,
+ QueryEnum.SEARCH_MUTABLE_STATUS_DATA_QUERY,
ColumnEnum.ENTRY_ID,
pageOwner.getEntryId("SearchDynamicWithMutableStatus")
)
+ mutableStatus.checkValue(
+ QueryEnum.SEARCH_MUTABLE_STATUS_DATA_QUERY, ColumnEnum.ENTRY_DISABLED, true.toString()
+ )
}
@Test
fun testQuerySearchIndexData() {
SpaEnvironmentFactory.reset(spaEnvironment)
+
val staticData = searchProvider.querySearchStaticData()
Truth.assertThat(staticData.count).isEqualTo(2)
+ staticData.moveToFirst()
+ staticData.checkValue(
+ QueryEnum.SEARCH_STATIC_DATA_QUERY,
+ ColumnEnum.ENTRY_ID,
+ pageOwner.getEntryId("SearchStaticWithNoStatus")
+ )
+ staticData.checkValue(
+ QueryEnum.SEARCH_STATIC_DATA_QUERY, ColumnEnum.SEARCH_TITLE, "SearchStaticWithNoStatus"
+ )
+ staticData.checkValue(
+ QueryEnum.SEARCH_STATIC_DATA_QUERY, ColumnEnum.SEARCH_KEYWORD, listOf("").toString()
+ )
+ staticData.checkValue(
+ QueryEnum.SEARCH_STATIC_DATA_QUERY,
+ ColumnEnum.SEARCH_PATH,
+ listOf("SearchStaticWithNoStatus", "SppForSearch").toString()
+ )
+ staticData.checkValue(
+ QueryEnum.SEARCH_STATIC_DATA_QUERY,
+ ColumnEnum.INTENT_TARGET_PACKAGE,
+ spaEnvironment.appContext.packageName
+ )
+ staticData.checkValue(
+ QueryEnum.SEARCH_STATIC_DATA_QUERY,
+ ColumnEnum.INTENT_TARGET_CLASS,
+ "com.android.settingslib.spa.tests.testutils.BlankActivity"
+ )
+
+ // Check extras in intent
+ val bundle =
+ staticData.getExtras(QueryEnum.SEARCH_STATIC_DATA_QUERY, ColumnEnum.INTENT_EXTRAS)
+ Truth.assertThat(bundle).isNotNull()
+ Truth.assertThat(bundle!!.size()).isEqualTo(3)
+ Truth.assertThat(bundle.getString("spaActivityDestination")).isEqualTo("SppForSearch")
+ Truth.assertThat(bundle.getString("highlightEntry"))
+ .isEqualTo(pageOwner.getEntryId("SearchStaticWithNoStatus"))
+ Truth.assertThat(bundle.getString("sessionSource")).isEqualTo("search")
+
+ staticData.moveToNext()
+ staticData.checkValue(
+ QueryEnum.SEARCH_STATIC_DATA_QUERY,
+ ColumnEnum.ENTRY_ID,
+ pageOwner.getEntryId("SearchStaticWithMutableStatus")
+ )
val dynamicData = searchProvider.querySearchDynamicData()
Truth.assertThat(dynamicData.count).isEqualTo(2)
+ dynamicData.moveToFirst()
+ dynamicData.checkValue(
+ QueryEnum.SEARCH_DYNAMIC_DATA_QUERY,
+ ColumnEnum.ENTRY_ID,
+ pageOwner.getEntryId("SearchDynamicWithMutableStatus")
+ )
+
+ dynamicData.moveToNext()
+ dynamicData.checkValue(
+ QueryEnum.SEARCH_DYNAMIC_DATA_QUERY,
+ ColumnEnum.ENTRY_ID,
+ pageOwner.getEntryId("SearchDynamicWithImmutableStatus")
+ )
+ dynamicData.checkValue(
+ QueryEnum.SEARCH_DYNAMIC_DATA_QUERY,
+ ColumnEnum.SEARCH_KEYWORD,
+ listOf("kw1", "kw2").toString()
+ )
}
}
private fun Cursor.checkValue(query: QueryEnum, column: ColumnEnum, value: String) {
- Truth.assertThat(getString(query.getIndex(column))).isEqualTo(value)
+ Truth.assertThat(getString(query.columnNames.indexOf(column))).isEqualTo(value)
+}
+
+private fun Cursor.getExtras(query: QueryEnum, column: ColumnEnum): Bundle? {
+ val extrasByte = getBlob(query.columnNames.indexOf(column)) ?: return null
+ val parcel = Parcel.obtain()
+ parcel.unmarshall(extrasByte, 0, extrasByte.size)
+ parcel.setDataPosition(0)
+ val bundle = Bundle()
+ bundle.readFromParcel(parcel)
+ return bundle
}
private fun SettingsPage.getEntryId(name: String): String {
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/UniqueIdHelper.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/UniqueIdHelper.kt
index 7e51fea69041..ce9b7917b769 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/UniqueIdHelper.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/UniqueIdHelper.kt
@@ -27,7 +27,7 @@ fun getUniquePageId(
parameter: List<NamedNavArgument> = emptyList(),
arguments: Bundle? = null
): String {
- val normArguments = parameter.normalize(arguments)
+ val normArguments = parameter.normalize(arguments, eraseRuntimeValues = true)
return "$name:${normArguments?.toString()}".toHashId()
}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/button/ActionButtonsTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/button/ActionButtonsTest.kt
index 8101a94a9461..f59b0decf1e5 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/button/ActionButtonsTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/button/ActionButtonsTest.kt
@@ -17,11 +17,13 @@
package com.android.settingslib.spa.widget.button
import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.Close
import androidx.compose.material.icons.outlined.Launch
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.getBoundsInRoot
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
@@ -66,4 +68,19 @@ class ActionButtonsTest {
assertThat(clicked).isTrue()
}
+
+ @Test
+ fun twoButtons_positionIsAligned() {
+ composeTestRule.setContent {
+ ActionButtons(
+ listOf(
+ ActionButton(text = "Open", imageVector = Icons.Outlined.Launch) {},
+ ActionButton(text = "Close", imageVector = Icons.Outlined.Close) {},
+ )
+ )
+ }
+
+ assertThat(composeTestRule.onNodeWithText("Open").getBoundsInRoot().top)
+ .isEqualTo(composeTestRule.onNodeWithText("Close").getBoundsInRoot().top)
+ }
}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ChartTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/chart/ChartTest.kt
index fa7a98add2ae..2230d6c96bbb 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ChartTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/chart/ChartTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.settingslib.spa.widget
+package com.android.settingslib.spa.widget.chart
import androidx.compose.ui.Modifier
import androidx.compose.ui.semantics.SemanticsPropertyKey
@@ -24,12 +24,6 @@ import androidx.compose.ui.test.SemanticsMatcher
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.android.settingslib.spa.widget.chart.BarChart
-import com.android.settingslib.spa.widget.chart.BarChartData
-import com.android.settingslib.spa.widget.chart.LineChart
-import com.android.settingslib.spa.widget.chart.LineChartData
-import com.android.settingslib.spa.widget.chart.PieChart
-import com.android.settingslib.spa.widget.chart.PieChartData
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -39,10 +33,10 @@ class ChartTest {
@get:Rule
val composeTestRule = createComposeRule()
- private val Chart = SemanticsPropertyKey<String>("Chart")
- private var SemanticsPropertyReceiver.chart by Chart
+ private val chart = SemanticsPropertyKey<String>("Chart")
+ private var SemanticsPropertyReceiver.chart by chart
private fun hasChart(chart: String): SemanticsMatcher =
- SemanticsMatcher.expectValue(Chart, chart)
+ SemanticsMatcher.expectValue(this.chart, chart)
@Test
fun line_chart_displayed() {
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/IllustrationTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/illustration/IllustrationTest.kt
index 54abec9ac0d7..105fdc8c69e0 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/IllustrationTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/illustration/IllustrationTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.settingslib.spa.widget
+package com.android.settingslib.spa.widget.illustration
import androidx.annotation.DrawableRes
import androidx.annotation.RawRes
@@ -29,7 +29,7 @@ import androidx.compose.ui.test.filterToOne
import androidx.compose.ui.test.hasAnyAncestor
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.android.settingslib.spa.tests.R
+import com.android.settingslib.spa.test.R
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -39,8 +39,8 @@ class IllustrationTest {
@get:Rule
val composeTestRule = createComposeRule()
- private val DrawableId = SemanticsPropertyKey<Int>("DrawableResId")
- private var SemanticsPropertyReceiver.drawableId by DrawableId
+ private val drawableId = SemanticsPropertyKey<Int>("DrawableResId")
+ private var SemanticsPropertyReceiver.drawableId by drawableId
@Test
fun image_displayed() {
@@ -54,7 +54,7 @@ class IllustrationTest {
}
fun hasDrawable(@DrawableRes id: Int): SemanticsMatcher =
- SemanticsMatcher.expectValue(DrawableId, id)
+ SemanticsMatcher.expectValue(drawableId, id)
val isIllustrationNode = hasAnyAncestor(hasDrawable(resId))
composeTestRule.onAllNodes(hasDrawable(resId))
@@ -62,8 +62,8 @@ class IllustrationTest {
.assertIsDisplayed()
}
- private val RawId = SemanticsPropertyKey<Int>("RawResId")
- private var SemanticsPropertyReceiver.rawId by RawId
+ private val rawId = SemanticsPropertyKey<Int>("RawResId")
+ private var SemanticsPropertyReceiver.rawId by rawId
@Test
fun empty_lottie_not_displayed() {
@@ -77,7 +77,7 @@ class IllustrationTest {
}
fun hasRaw(@RawRes id: Int): SemanticsMatcher =
- SemanticsMatcher.expectValue(RawId, id)
+ SemanticsMatcher.expectValue(rawId, id)
val isIllustrationNode = hasAnyAncestor(hasRaw(resId))
composeTestRule.onAllNodes(hasRaw(resId))
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/HomeScaffoldTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/HomeScaffoldTest.kt
new file mode 100644
index 000000000000..6ccbbd095c49
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/HomeScaffoldTest.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.scaffold
+
+import androidx.compose.material3.Text
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class HomeScaffoldTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ @Test
+ fun title_isDisplayed() {
+ composeTestRule.setContent {
+ HomeScaffold(title = TITLE) {
+ Text(text = "AAA")
+ Text(text = "BBB")
+ }
+ }
+
+ composeTestRule.onNodeWithText(TITLE).assertIsDisplayed()
+ }
+
+ @Test
+ fun items_areDisplayed() {
+ composeTestRule.setContent {
+ HomeScaffold(title = TITLE) {
+ Text(text = "AAA")
+ Text(text = "BBB")
+ }
+ }
+
+ composeTestRule.onNodeWithText("AAA").assertIsDisplayed()
+ composeTestRule.onNodeWithText("BBB").assertIsDisplayed()
+ }
+
+ private companion object {
+ const val TITLE = "title"
+ }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/FooterTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/FooterTest.kt
new file mode 100644
index 000000000000..9d0501f029b1
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/FooterTest.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.ui
+
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsNotDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.onRoot
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class FooterTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ @Test
+ fun footer_isEmpty() {
+ composeTestRule.setContent {
+ Footer(footerText = "")
+ }
+
+ composeTestRule.onRoot().assertIsNotDisplayed()
+ }
+
+ @Test
+ fun footer_notEmpty_displayed() {
+ composeTestRule.setContent {
+ Footer(footerText = FOOTER_TEXT)
+ }
+
+ composeTestRule.onNodeWithText(FOOTER_TEXT).assertIsDisplayed()
+ }
+
+ private companion object {
+ const val FOOTER_TEXT = "Footer text"
+ }
+}
diff --git a/packages/SettingsLib/Spa/testutils/Android.bp b/packages/SettingsLib/Spa/testutils/Android.bp
index de87ddedc122..2c1e1c2abc2c 100644
--- a/packages/SettingsLib/Spa/testutils/Android.bp
+++ b/packages/SettingsLib/Spa/testutils/Android.bp
@@ -24,7 +24,9 @@ android_library {
srcs: ["src/**/*.kt"],
static_libs: [
+ "SpaLib",
"androidx.arch.core_core-testing",
+ "androidx.compose.runtime_runtime",
"androidx.compose.ui_ui-test-junit4",
"androidx.compose.ui_ui-test-manifest",
"mockito",
diff --git a/packages/SettingsLib/Spa/testutils/build.gradle b/packages/SettingsLib/Spa/testutils/build.gradle
index 81e54c13a625..dd7058db3f56 100644
--- a/packages/SettingsLib/Spa/testutils/build.gradle
+++ b/packages/SettingsLib/Spa/testutils/build.gradle
@@ -44,9 +44,17 @@ android {
jvmTarget = '1.8'
freeCompilerArgs = ["-Xjvm-default=all"]
}
+ buildFeatures {
+ compose true
+ }
+ composeOptions {
+ kotlinCompilerExtensionVersion jetpack_compose_compiler_version
+ }
}
dependencies {
+ api project(":spa")
+
api "androidx.arch.core:core-testing:2.1.0"
api "androidx.compose.ui:ui-test-junit4:$jetpack_compose_version"
api "com.google.truth:truth:1.1.3"
diff --git a/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/FakeNavControllerWrapper.kt b/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/FakeNavControllerWrapper.kt
new file mode 100644
index 000000000000..5a3044d5ef84
--- /dev/null
+++ b/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/FakeNavControllerWrapper.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.testutils
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import com.android.settingslib.spa.framework.compose.LocalNavController
+import com.android.settingslib.spa.framework.compose.NavControllerWrapper
+
+class FakeNavControllerWrapper : NavControllerWrapper {
+ var navigateCalledWith: String? = null
+ var navigateBackIsCalled = false
+
+ override fun navigate(route: String) {
+ navigateCalledWith = route
+ }
+
+ override fun navigateBack() {
+ navigateBackIsCalled = true
+ }
+
+ @Composable
+ fun Wrapper(content: @Composable () -> Unit) {
+ CompositionLocalProvider(LocalNavController provides this) {
+ content()
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/NewFooterActionsController.kt b/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/FlowTestUtil.kt
index 7c67d9f42b55..7a114997c44c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/NewFooterActionsController.kt
+++ b/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/FlowTestUtil.kt
@@ -14,20 +14,13 @@
* limitations under the License.
*/
-package com.android.systemui.qs
+package com.android.settingslib.spa.testutils
-import com.android.systemui.dagger.SysUISingleton
-import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.withTimeoutOrNull
-/** Controller for the footer actions. This manages the initialization of its dependencies. */
-@SysUISingleton
-class NewFooterActionsController
-@Inject
-// TODO(b/242040009): Rename this to FooterActionsController.
-constructor(
- private val fgsManagerController: FgsManagerController,
-) {
- fun init() {
- fgsManagerController.init()
+suspend fun <T> Flow<T>.firstWithTimeoutOrNull(timeMillis: Long = 500): T? =
+ withTimeoutOrNull(timeMillis) {
+ first()
}
-}
diff --git a/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/LiveDataTestUtil.kt b/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/LiveDataTestUtil.kt
new file mode 100644
index 000000000000..dddda5511c7b
--- /dev/null
+++ b/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/LiveDataTestUtil.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.testutils
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.Observer
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+import java.util.concurrent.TimeoutException
+
+fun <T> LiveData<T>.getOrAwaitValue(
+ timeout: Long = 1,
+ timeUnit: TimeUnit = TimeUnit.SECONDS,
+ afterObserve: () -> Unit = {},
+): T? {
+ var data: T? = null
+ val latch = CountDownLatch(1)
+ val observer = Observer<T> { newData ->
+ data = newData
+ latch.countDown()
+ }
+ this.observeForever(observer)
+
+ afterObserve()
+
+ try {
+ // Don't wait indefinitely if the LiveData is not set.
+ if (!latch.await(timeout, timeUnit)) {
+ throw TimeoutException("LiveData value was never set.")
+ }
+ } finally {
+ this.removeObserver(observer)
+ }
+
+ return data
+}
diff --git a/packages/SettingsLib/Spa/testutils/src/MockitoHelper.kt b/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/MockitoHelper.kt
index 5ba54c12b0d0..5ba54c12b0d0 100644
--- a/packages/SettingsLib/Spa/testutils/src/MockitoHelper.kt
+++ b/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/MockitoHelper.kt
diff --git a/packages/SettingsLib/Spa/testutils/src/SpaTest.kt b/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/SpaTest.kt
index a397bb48103c..a397bb48103c 100644
--- a/packages/SettingsLib/Spa/testutils/src/SpaTest.kt
+++ b/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/SpaTest.kt
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-af/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-af/strings.xml
new file mode 100644
index 000000000000..3ad7bb013219
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-af/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"Geen apps nie."</string>
+ <string name="menu_show_system" msgid="906304605807554788">"Wys stelsel"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"Versteek stelsel"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Toegelaat"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Nie toegelaat nie"</string>
+ <string name="version_text" msgid="4001669804596458577">"weergawe <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-am/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-am/strings.xml
new file mode 100644
index 000000000000..4c2525bfd861
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-am/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"መተግበሪያዎች የሉም።"</string>
+ <string name="menu_show_system" msgid="906304605807554788">"ሥርዓትን አሳይ"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"ሥርዓትን ደብቅ"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"ይፈቀዳል"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"አይፈቀድም"</string>
+ <string name="version_text" msgid="4001669804596458577">"ሥሪት <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-ar/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-ar/strings.xml
new file mode 100644
index 000000000000..436914d96359
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-ar/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"ليس هناك أي تطبيقات."</string>
+ <string name="menu_show_system" msgid="906304605807554788">"إظهار عمليات النظام"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"إخفاء عمليات النظام"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"مسموح به"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"غير مسموح به"</string>
+ <string name="version_text" msgid="4001669804596458577">"الإصدار <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-as/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-as/strings.xml
new file mode 100644
index 000000000000..c1c88f29b05c
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-as/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"কোনো এপ্‌ নাই।"</string>
+ <string name="menu_show_system" msgid="906304605807554788">"ছিষ্টেম দেখুৱাওক"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"ছিষ্টেম লুকুৱাওক"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"অনুমতি দিয়া হৈছে"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"অনুমতি নাই"</string>
+ <string name="version_text" msgid="4001669804596458577">"সংস্কৰণ <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-az/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-az/strings.xml
new file mode 100644
index 000000000000..4fc090a8b32b
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-az/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"Tətbiq yoxdur."</string>
+ <string name="menu_show_system" msgid="906304605807554788">"Sistemi göstərin"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"Sistemi gizlədin"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"İcazə verildi"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"İcazə verilməyib"</string>
+ <string name="version_text" msgid="4001669804596458577">"versiya <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-b+sr+Latn/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-b+sr+Latn/strings.xml
new file mode 100644
index 000000000000..370850398487
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-b+sr+Latn/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"Nema aplikacija."</string>
+ <string name="menu_show_system" msgid="906304605807554788">"Prikaži sistemske"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"Sakrij sistemske"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Dozvoljeno"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Nije dozvoljeno"</string>
+ <string name="version_text" msgid="4001669804596458577">"verzija <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-be/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-be/strings.xml
new file mode 100644
index 000000000000..38fb12bacc05
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-be/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"Няма праграм."</string>
+ <string name="menu_show_system" msgid="906304605807554788">"Паказаць сістэмныя працэсы"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"Схаваць сістэмныя працэсы"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Дазволена"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Забаронена"</string>
+ <string name="version_text" msgid="4001669804596458577">"версія <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-bg/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-bg/strings.xml
new file mode 100644
index 000000000000..b9b03bfdb26f
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-bg/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"Няма приложения."</string>
+ <string name="menu_show_system" msgid="906304605807554788">"Показване на системните процеси"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"Скриване на системните процеси"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Разрешено"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Не е разрешено"</string>
+ <string name="version_text" msgid="4001669804596458577">"версия <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-bn/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-bn/strings.xml
new file mode 100644
index 000000000000..b805b3c90b29
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-bn/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"কোনও অ্যাপ নেই।"</string>
+ <string name="menu_show_system" msgid="906304605807554788">"সিস্টেম দেখুন"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"সিস্টেম লুকান"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"অনুমোদিত"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"অননুমোদিত"</string>
+ <string name="version_text" msgid="4001669804596458577">"ভার্সন <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-bs/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-bs/strings.xml
new file mode 100644
index 000000000000..9ceb3406ed5d
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-bs/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"Nema aplikacija."</string>
+ <string name="menu_show_system" msgid="906304605807554788">"Prikaži sistemske aplikacije"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"Sakrij sistemske aplikacije"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Dozvoljeno"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Nije dozvoljeno"</string>
+ <string name="version_text" msgid="4001669804596458577">"verzija <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-ca/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-ca/strings.xml
new file mode 100644
index 000000000000..00cb41b56777
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-ca/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"No hi ha cap aplicació."</string>
+ <string name="menu_show_system" msgid="906304605807554788">"Mostra el sistema"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"Amaga el sistema"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Amb permís"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Sense permís"</string>
+ <string name="version_text" msgid="4001669804596458577">"versió <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-cs/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-cs/strings.xml
new file mode 100644
index 000000000000..7b28f11b3b32
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-cs/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"Žádné aplikace"</string>
+ <string name="menu_show_system" msgid="906304605807554788">"Zobrazit systémové aplikace"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"Skrýt systémové aplikace"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Povoleno"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Nepovoleno"</string>
+ <string name="version_text" msgid="4001669804596458577">"verze <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-da/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-da/strings.xml
new file mode 100644
index 000000000000..f1893bea688d
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-da/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"Ingen apps."</string>
+ <string name="menu_show_system" msgid="906304605807554788">"Vis system"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"Skjul system"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Tilladt"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Ikke tilladt"</string>
+ <string name="version_text" msgid="4001669804596458577">"version <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-de/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-de/strings.xml
new file mode 100644
index 000000000000..471a7a7fce4d
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-de/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"Keine Apps."</string>
+ <string name="menu_show_system" msgid="906304605807554788">"System-Apps anzeigen"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"System-Apps ausblenden"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Zulässig"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Nicht zulässig"</string>
+ <string name="version_text" msgid="4001669804596458577">"Version <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-el/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-el/strings.xml
new file mode 100644
index 000000000000..6c46e27feafe
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-el/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"Δεν υπάρχουν εφαρμογές."</string>
+ <string name="menu_show_system" msgid="906304605807554788">"Εμφάνιση συστήματος"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"Απόκρυψη συστήματος"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Επιτρέπεται"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Δεν επιτρέπεται"</string>
+ <string name="version_text" msgid="4001669804596458577">"έκδοση <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-en-rAU/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-en-rAU/strings.xml
new file mode 100644
index 000000000000..de48ff1807c9
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-en-rAU/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"No apps."</string>
+ <string name="menu_show_system" msgid="906304605807554788">"Show system"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"Hide system"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Allowed"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Not allowed"</string>
+ <string name="version_text" msgid="4001669804596458577">"Version <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-en-rGB/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-en-rGB/strings.xml
new file mode 100644
index 000000000000..de48ff1807c9
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-en-rGB/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"No apps."</string>
+ <string name="menu_show_system" msgid="906304605807554788">"Show system"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"Hide system"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Allowed"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Not allowed"</string>
+ <string name="version_text" msgid="4001669804596458577">"Version <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-en-rIN/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-en-rIN/strings.xml
new file mode 100644
index 000000000000..de48ff1807c9
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-en-rIN/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"No apps."</string>
+ <string name="menu_show_system" msgid="906304605807554788">"Show system"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"Hide system"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Allowed"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Not allowed"</string>
+ <string name="version_text" msgid="4001669804596458577">"Version <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-es-rUS/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-es-rUS/strings.xml
new file mode 100644
index 000000000000..d211eeb37062
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-es-rUS/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"No hay apps."</string>
+ <string name="menu_show_system" msgid="906304605807554788">"Mostrar sistema"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"Ocultar sistema"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Permitida"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"No se permite"</string>
+ <string name="version_text" msgid="4001669804596458577">"versión <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-es/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-es/strings.xml
new file mode 100644
index 000000000000..d907cb887411
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-es/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"No hay aplicaciones."</string>
+ <string name="menu_show_system" msgid="906304605807554788">"Mostrar sistema"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"Ocultar sistema"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Permitida"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"No permitida"</string>
+ <string name="version_text" msgid="4001669804596458577">"versión <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-et/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-et/strings.xml
new file mode 100644
index 000000000000..2be2967d3a5a
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-et/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"Rakendusi pole."</string>
+ <string name="menu_show_system" msgid="906304605807554788">"Kuva süsteem"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"Peida süsteem"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Lubatud"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Pole lubatud"</string>
+ <string name="version_text" msgid="4001669804596458577">"versioon <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-eu/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-eu/strings.xml
new file mode 100644
index 000000000000..7fb2ee28e293
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-eu/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"Ez dago aplikaziorik."</string>
+ <string name="menu_show_system" msgid="906304605807554788">"Erakutsi sistemaren aplikazioak"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"Ezkutatu sistemaren aplikazioak"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Baimena dauka"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Ez dauka baimenik"</string>
+ <string name="version_text" msgid="4001669804596458577">"<xliff:g id="VERSION_NUM">%1$s</xliff:g> bertsioa"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-fa/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-fa/strings.xml
new file mode 100644
index 000000000000..7711cac00e0f
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-fa/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"برنامه‌ای وجود ندارد."</string>
+ <string name="menu_show_system" msgid="906304605807554788">"نمایش سیستم"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"پنهان کردن سیستم"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"مجاز"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"غیرمجاز"</string>
+ <string name="version_text" msgid="4001669804596458577">"نسخه <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-fi/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-fi/strings.xml
new file mode 100644
index 000000000000..af65bfe53eb2
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-fi/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"Ei sovelluksia."</string>
+ <string name="menu_show_system" msgid="906304605807554788">"Näytä järjestelmä"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"Piilota järjestelmä"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Sallittu"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Ei sallittu"</string>
+ <string name="version_text" msgid="4001669804596458577">"versio <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-fr-rCA/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-fr-rCA/strings.xml
new file mode 100644
index 000000000000..31f1ee08a3fd
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-fr-rCA/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"Aucune application"</string>
+ <string name="menu_show_system" msgid="906304605807554788">"Afficher le système"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"Masquer le système"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Autorisé"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Non autorisée"</string>
+ <string name="version_text" msgid="4001669804596458577">"version <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-fr/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-fr/strings.xml
new file mode 100644
index 000000000000..5b7b5e61e52a
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-fr/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"Aucune appli."</string>
+ <string name="menu_show_system" msgid="906304605807554788">"Afficher le système"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"Masquer le système"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Autorisé"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Non autorisé"</string>
+ <string name="version_text" msgid="4001669804596458577">"version <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-gl/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-gl/strings.xml
new file mode 100644
index 000000000000..3d1f9d88def0
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-gl/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"Ningunha aplicación"</string>
+ <string name="menu_show_system" msgid="906304605807554788">"Mostrar sistema"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"Ocultar sistema"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Permitida"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Non permitida"</string>
+ <string name="version_text" msgid="4001669804596458577">"versión <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-gu/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-gu/strings.xml
new file mode 100644
index 000000000000..c598b70d0f9b
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-gu/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"કોઈ ઍપ નથી."</string>
+ <string name="menu_show_system" msgid="906304605807554788">"સિસ્ટમ બતાવો"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"સિસ્ટમ છુપાવો"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"મંજૂરી છે"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"મંજૂરી નથી"</string>
+ <string name="version_text" msgid="4001669804596458577">"વર્ઝન <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-hi/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-hi/strings.xml
new file mode 100644
index 000000000000..809afd344e61
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-hi/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"कोई ऐप्लिकेशन नहीं है."</string>
+ <string name="menu_show_system" msgid="906304605807554788">"सिस्टम के ऐप्लिकेशन दिखाएं"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"सिस्टम के ऐप्लिकेशन छिपाएं"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"अनुमति है"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"अनुमति नहीं है"</string>
+ <string name="version_text" msgid="4001669804596458577">"वर्शन <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-hr/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-hr/strings.xml
new file mode 100644
index 000000000000..1a87974d6bf9
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-hr/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"Nema aplikacija."</string>
+ <string name="menu_show_system" msgid="906304605807554788">"Prikaži sustav"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"Sakrij sustav"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Dopušteno"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Nije dopušteno"</string>
+ <string name="version_text" msgid="4001669804596458577">"verzija <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-hu/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-hu/strings.xml
new file mode 100644
index 000000000000..1ae7cdfcc476
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-hu/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"Nincsenek alkalmazások."</string>
+ <string name="menu_show_system" msgid="906304605807554788">"Rendszerfolyamatok megjelenítése"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"Rendszerfolyamatok elrejtése"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Engedélyezve"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Nem engedélyezett"</string>
+ <string name="version_text" msgid="4001669804596458577">"verzió: <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-hy/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-hy/strings.xml
new file mode 100644
index 000000000000..353af77fdd21
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-hy/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"Հավելվածներ չկան։"</string>
+ <string name="menu_show_system" msgid="906304605807554788">"Ցույց տալ համակարգային գործընթացները"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"Թաքցնել համակարգային գործընթացները"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Թույլատրված է"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Արգելված է"</string>
+ <string name="version_text" msgid="4001669804596458577">"տարբերակ <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-in/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-in/strings.xml
new file mode 100644
index 000000000000..8b766b047430
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-in/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"Tidak ada aplikasi."</string>
+ <string name="menu_show_system" msgid="906304605807554788">"Tampilkan sistem"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"Sembunyikan sistem"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Diizinkan"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Tidak diizinkan"</string>
+ <string name="version_text" msgid="4001669804596458577">"versi <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-is/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-is/strings.xml
new file mode 100644
index 000000000000..cbd412d1bf1c
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-is/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"Engin forrit."</string>
+ <string name="menu_show_system" msgid="906304605807554788">"Sýna kerfi"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"Fela kerfi"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Leyft"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Ekki leyft"</string>
+ <string name="version_text" msgid="4001669804596458577">"útgáfa <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-it/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-it/strings.xml
new file mode 100644
index 000000000000..d83c70ddf7b1
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-it/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"Nessuna app."</string>
+ <string name="menu_show_system" msgid="906304605807554788">"Mostra sistema"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"Nascondi sistema"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Consentita"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Non consentita"</string>
+ <string name="version_text" msgid="4001669804596458577">"versione <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-iw/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-iw/strings.xml
new file mode 100644
index 000000000000..7ed8a6bebd63
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-iw/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"אין אפליקציות."</string>
+ <string name="menu_show_system" msgid="906304605807554788">"הצגת תהליכי מערכת"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"הסתרת תהליכי מערכת"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"יש הרשאה"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"אין הרשאה"</string>
+ <string name="version_text" msgid="4001669804596458577">"גרסה <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-ja/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-ja/strings.xml
new file mode 100644
index 000000000000..b12cb5cbacd2
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-ja/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"アプリはありません。"</string>
+ <string name="menu_show_system" msgid="906304605807554788">"システムアプリを表示"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"システムアプリを表示しない"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"許可"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"許可しない"</string>
+ <string name="version_text" msgid="4001669804596458577">"バージョン <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-ka/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-ka/strings.xml
new file mode 100644
index 000000000000..c01a028fa366
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-ka/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"არ არის აპები."</string>
+ <string name="menu_show_system" msgid="906304605807554788">"სისტემის ჩვენება"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"სისტემის დამალვა"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"დაშვებულია"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"დაუშვებელია"</string>
+ <string name="version_text" msgid="4001669804596458577">"<xliff:g id="VERSION_NUM">%1$s</xliff:g> ვერსია"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-kk/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-kk/strings.xml
new file mode 100644
index 000000000000..fb94404cd3ab
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-kk/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"Қолданба жоқ."</string>
+ <string name="menu_show_system" msgid="906304605807554788">"Жүйені көрсету"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"Жүйені жасыру"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Рұқсат етілген"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Рұқсат етілмеген"</string>
+ <string name="version_text" msgid="4001669804596458577">"<xliff:g id="VERSION_NUM">%1$s</xliff:g> нұсқасы"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-km/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-km/strings.xml
new file mode 100644
index 000000000000..610123ddd7f6
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-km/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"គ្មាន​កម្មវិធី។"</string>
+ <string name="menu_show_system" msgid="906304605807554788">"បង្ហាញ​ប្រព័ន្ធ"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"លាក់ប្រព័ន្ធ"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"បាន​អនុញ្ញាត"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"មិន​អនុញ្ញាត​ទេ"</string>
+ <string name="version_text" msgid="4001669804596458577">"កំណែ <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-kn/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-kn/strings.xml
new file mode 100644
index 000000000000..b61c0088a757
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-kn/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"ಯಾವುದೇ ಆ್ಯಪ್‍‍ಗಳಿಲ್ಲ."</string>
+ <string name="menu_show_system" msgid="906304605807554788">"ಸಿಸ್ಟಂ ತೋರಿಸಿ"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"ಸಿಸ್ಟಂ ಮರೆಮಾಡಿ"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"ಅನುಮತಿಸಲಾಗಿದೆ"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"ಅನುಮತಿಸಲಾಗುವುದಿಲ್ಲ"</string>
+ <string name="version_text" msgid="4001669804596458577">"ಆವೃತ್ತಿ <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-ko/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-ko/strings.xml
new file mode 100644
index 000000000000..660dc0ef18c8
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-ko/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"앱 없음"</string>
+ <string name="menu_show_system" msgid="906304605807554788">"시스템 표시"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"시스템 숨기기"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"허용됨"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"허용되지 않음"</string>
+ <string name="version_text" msgid="4001669804596458577">"버전 <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-ky/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-ky/strings.xml
new file mode 100644
index 000000000000..898ec09f931a
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-ky/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"Бир дагы колдонмо жок."</string>
+ <string name="menu_show_system" msgid="906304605807554788">"Системаны көрсөтүү"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"Системаны жашыруу"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Уруксат берилген"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Тыюу салынган"</string>
+ <string name="version_text" msgid="4001669804596458577">"<xliff:g id="VERSION_NUM">%1$s</xliff:g> версиясы"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-lo/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-lo/strings.xml
new file mode 100644
index 000000000000..d0f77e4660d9
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-lo/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"ບໍ່ມີແອັບ."</string>
+ <string name="menu_show_system" msgid="906304605807554788">"ສະແດງລະບົບ"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"ເຊື່ອງລະບົບ"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"ອະນຸຍາດແລ້ວ"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"ບໍ່ໄດ້ອະນຸຍາດ"</string>
+ <string name="version_text" msgid="4001669804596458577">"ເວີຊັນ <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-lt/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-lt/strings.xml
new file mode 100644
index 000000000000..7d33f91611b8
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-lt/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"Nėra programų"</string>
+ <string name="menu_show_system" msgid="906304605807554788">"Rodyti sistemą"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"Slėpti sistemą"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Leidžiama"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Neleidžiama"</string>
+ <string name="version_text" msgid="4001669804596458577">"<xliff:g id="VERSION_NUM">%1$s</xliff:g> versija"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-lv/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-lv/strings.xml
new file mode 100644
index 000000000000..66dc44df2235
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-lv/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"Nav lietotņu."</string>
+ <string name="menu_show_system" msgid="906304605807554788">"Rādīt sistēmas procesus"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"Slēpt sistēmas procesus"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Atļauja piešķirta"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Atļauja nav piešķirta"</string>
+ <string name="version_text" msgid="4001669804596458577">"versija <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-mk/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-mk/strings.xml
new file mode 100644
index 000000000000..db55600015d2
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-mk/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"Нема апликации."</string>
+ <string name="menu_show_system" msgid="906304605807554788">"Прикажи го системот"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"Сокриј го системот"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Со дозволен пристап"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Без дозволен пристап"</string>
+ <string name="version_text" msgid="4001669804596458577">"верзија <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-ml/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-ml/strings.xml
new file mode 100644
index 000000000000..c99d0d31d7e6
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-ml/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"ആപ്പുകളൊന്നുമില്ല."</string>
+ <string name="menu_show_system" msgid="906304605807554788">"സിസ്‌റ്റം കാണിക്കുക"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"സിസ്‌റ്റം മറയ്ക്കുക"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"അനുവാദം ലഭിച്ചു"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"അനുവാദം ലഭിച്ചില്ല"</string>
+ <string name="version_text" msgid="4001669804596458577">"പതിപ്പ് <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-mn/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-mn/strings.xml
new file mode 100644
index 000000000000..bc0864bb5dc9
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-mn/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"Апп байхгүй."</string>
+ <string name="menu_show_system" msgid="906304605807554788">"Системийг харуулах"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"Системийг нуух"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Зөвшөөрсөн"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Зөвшөөрөөгүй"</string>
+ <string name="version_text" msgid="4001669804596458577">"хувилбар <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-mr/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-mr/strings.xml
new file mode 100644
index 000000000000..55a178bff503
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-mr/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"अ‍ॅप्स नाहीत."</string>
+ <string name="menu_show_system" msgid="906304605807554788">"सिस्टीम दाखवा"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"सिस्टीम लपवा"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"अनुमती असलेले"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"अनुमती नाही"</string>
+ <string name="version_text" msgid="4001669804596458577">"आवृत्ती <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-ms/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-ms/strings.xml
new file mode 100644
index 000000000000..a736de973a78
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-ms/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"Tiada aplikasi."</string>
+ <string name="menu_show_system" msgid="906304605807554788">"Tunjukkan sistem"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"Sembunyikan sistem"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Dibenarkan"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Tidak dibenarkan"</string>
+ <string name="version_text" msgid="4001669804596458577">"versi <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-my/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-my/strings.xml
new file mode 100644
index 000000000000..3bed608935c8
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-my/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"အက်ပ်မရှိပါ။"</string>
+ <string name="menu_show_system" msgid="906304605807554788">"စနစ်ကိုပြပါ"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"စနစ်ကိုဖျောက်ထားရန်"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"ခွင့်ပြုထားသည်"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"ခွင့်ပြုမထားပါ"</string>
+ <string name="version_text" msgid="4001669804596458577">"ဗားရှင်း <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-nb/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-nb/strings.xml
new file mode 100644
index 000000000000..d6bd063242b1
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-nb/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"Ingen apper."</string>
+ <string name="menu_show_system" msgid="906304605807554788">"Vis systemprosesser"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"Skjul systemprosesser"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Tillatt"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Ikke tillatt"</string>
+ <string name="version_text" msgid="4001669804596458577">"versjon <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-ne/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-ne/strings.xml
new file mode 100644
index 000000000000..97db4ea10985
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-ne/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"कुनै पनि एप छैन।"</string>
+ <string name="menu_show_system" msgid="906304605807554788">"सिस्टम देखाइयोस्"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"सिस्टम लुकाइयोस्"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"अनुमति दिइएका एप"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"अनुमति नदिइएका एप"</string>
+ <string name="version_text" msgid="4001669804596458577">"संस्करण <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-nl/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-nl/strings.xml
new file mode 100644
index 000000000000..7157e3fc377f
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-nl/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"Geen apps."</string>
+ <string name="menu_show_system" msgid="906304605807554788">"Systeem-apps tonen"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"Systeem-apps verbergen"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Toegestaan"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Niet toegestaan"</string>
+ <string name="version_text" msgid="4001669804596458577">"versie <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-or/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-or/strings.xml
new file mode 100644
index 000000000000..24779e3e634a
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-or/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"କୌଣସି ଆପ୍ସ ନାହିଁ।"</string>
+ <string name="menu_show_system" msgid="906304605807554788">"ସିଷ୍ଟମ ଦେଖାନ୍ତୁ"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"ସିଷ୍ଟମକୁ ଲୁଚାନ୍ତୁ"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"ଅନୁମତି ଦିଆଯାଇଛି"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"ଅନୁମତି ଦିଆଯାଇନାହିଁ"</string>
+ <string name="version_text" msgid="4001669804596458577">"ଭର୍ସନ <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-pa/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-pa/strings.xml
new file mode 100644
index 000000000000..fe1a3eb91411
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-pa/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"ਕੋਈ ਐਪ ਨਹੀਂ।"</string>
+ <string name="menu_show_system" msgid="906304605807554788">"ਸਿਸਟਮ ਦਿਖਾਓ"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"ਸਿਸਟਮ ਲੁਕਾਓ"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"ਆਗਿਆ ਹੈ"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"ਆਗਿਆ ਨਹੀਂ ਹੈ"</string>
+ <string name="version_text" msgid="4001669804596458577">"ਵਰਜਨ <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-pl/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-pl/strings.xml
new file mode 100644
index 000000000000..9d5ba9de1bc5
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-pl/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"Brak aplikacji."</string>
+ <string name="menu_show_system" msgid="906304605807554788">"Pokaż systemowe"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"Ukryj systemowe"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Dozwolone"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Niedozwolone"</string>
+ <string name="version_text" msgid="4001669804596458577">"wersja <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-pt-rBR/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-pt-rBR/strings.xml
new file mode 100644
index 000000000000..18a31d8f7ef5
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-pt-rBR/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"Nenhum app."</string>
+ <string name="menu_show_system" msgid="906304605807554788">"Mostrar sistema"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"Ocultar sistema"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Permitido"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Não permitido"</string>
+ <string name="version_text" msgid="4001669804596458577">"Versão <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-pt-rPT/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-pt-rPT/strings.xml
new file mode 100644
index 000000000000..ddf5e51cceff
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-pt-rPT/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"Sem apps."</string>
+ <string name="menu_show_system" msgid="906304605807554788">"Mostrar sistema"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"Ocultar sistema"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Permitida"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Não permitida"</string>
+ <string name="version_text" msgid="4001669804596458577">"versão <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-pt/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-pt/strings.xml
new file mode 100644
index 000000000000..18a31d8f7ef5
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-pt/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"Nenhum app."</string>
+ <string name="menu_show_system" msgid="906304605807554788">"Mostrar sistema"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"Ocultar sistema"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Permitido"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Não permitido"</string>
+ <string name="version_text" msgid="4001669804596458577">"Versão <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-ro/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-ro/strings.xml
new file mode 100644
index 000000000000..ef58fb8e1e9c
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-ro/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"Nu există aplicații."</string>
+ <string name="menu_show_system" msgid="906304605807554788">"Afișează procesele de sistem"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"Ascunde procesele de sistem"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Permisă"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Nepermisă"</string>
+ <string name="version_text" msgid="4001669804596458577">"versiunea <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-ru/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-ru/strings.xml
new file mode 100644
index 000000000000..6d69c80dcd19
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-ru/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"Нет приложений."</string>
+ <string name="menu_show_system" msgid="906304605807554788">"Показать системные процессы"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"Скрыть системные процессы"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Разрешено"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Запрещено"</string>
+ <string name="version_text" msgid="4001669804596458577">"версия <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-si/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-si/strings.xml
new file mode 100644
index 000000000000..d2c20e669630
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-si/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"යෙදුම් නොමැත."</string>
+ <string name="menu_show_system" msgid="906304605807554788">"පද්ධතිය පෙන්වන්න"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"පද්ධතිය සඟවන්න"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"ඉඩ දුන්"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"ඉඩ නොදෙන"</string>
+ <string name="version_text" msgid="4001669804596458577">"<xliff:g id="VERSION_NUM">%1$s</xliff:g> අනුවාදය"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-sk/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-sk/strings.xml
new file mode 100644
index 000000000000..0d0984f3171c
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-sk/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"Žiadne aplikácie."</string>
+ <string name="menu_show_system" msgid="906304605807554788">"Zobraziť systémové procesy"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"Skryť systémové procesy"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Povolené"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Nepovolené"</string>
+ <string name="version_text" msgid="4001669804596458577">"verzia <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-sl/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-sl/strings.xml
new file mode 100644
index 000000000000..c8bd15ada862
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-sl/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"Ni aplikacij."</string>
+ <string name="menu_show_system" msgid="906304605807554788">"Prikaži sistemske procese"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"Skrij sistemske procese"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Dovoljeno"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Ni dovoljeno"</string>
+ <string name="version_text" msgid="4001669804596458577">"različica <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-sq/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-sq/strings.xml
new file mode 100644
index 000000000000..112868a78e06
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-sq/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"Asnjë aplikacion"</string>
+ <string name="menu_show_system" msgid="906304605807554788">"Shfaq sistemin"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"Fshih sistemin"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Lejohet"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Nuk lejohet"</string>
+ <string name="version_text" msgid="4001669804596458577">"versioni <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-sr/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-sr/strings.xml
new file mode 100644
index 000000000000..4c99d60e142c
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-sr/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"Нема апликација."</string>
+ <string name="menu_show_system" msgid="906304605807554788">"Прикажи системске"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"Сакриј системске"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Дозвољено"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Није дозвољено"</string>
+ <string name="version_text" msgid="4001669804596458577">"верзија <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-sv/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-sv/strings.xml
new file mode 100644
index 000000000000..1dd5efdfe4a4
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-sv/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"Inga appar."</string>
+ <string name="menu_show_system" msgid="906304605807554788">"Visa systemet"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"Dölj systemet"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Tillåts"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Tillåts inte"</string>
+ <string name="version_text" msgid="4001669804596458577">"version <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-sw/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-sw/strings.xml
new file mode 100644
index 000000000000..a0ee70c42925
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-sw/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"Hakuna programu yoyote."</string>
+ <string name="menu_show_system" msgid="906304605807554788">"Onyesha mfumo"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"Ficha mfumo"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Inaruhusiwa"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Hairuhusiwi"</string>
+ <string name="version_text" msgid="4001669804596458577">"toleo la <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-ta/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-ta/strings.xml
new file mode 100644
index 000000000000..36d64e870d82
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-ta/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"ஆப்ஸ் இல்லை."</string>
+ <string name="menu_show_system" msgid="906304605807554788">"சிஸ்டம் ஆப்ஸைக் காட்டு"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"சிஸ்டம் ஆப்ஸை மறை"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"அனுமதிக்கப்பட்டது"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"அனுமதிக்கப்படவில்லை"</string>
+ <string name="version_text" msgid="4001669804596458577">"பதிப்பு <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-te/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-te/strings.xml
new file mode 100644
index 000000000000..a908dd4c9a65
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-te/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"యాప్‌లు ఏవి లేవు."</string>
+ <string name="menu_show_system" msgid="906304605807554788">"సిస్టమ్ ప్రాసెస్‌లను చూపించండి"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"సిస్టమ్‌ను దాచండి"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"అనుమతించబడినవి"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"అనుమతించబడలేదు"</string>
+ <string name="version_text" msgid="4001669804596458577">"వెర్షన్ <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-th/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-th/strings.xml
new file mode 100644
index 000000000000..1d7db1adf210
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-th/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"ไม่มีแอป"</string>
+ <string name="menu_show_system" msgid="906304605807554788">"แสดงระบบ"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"ซ่อนระบบ"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"อนุญาต"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"ไม่อนุญาต"</string>
+ <string name="version_text" msgid="4001669804596458577">"เวอร์ชัน <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-tl/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-tl/strings.xml
new file mode 100644
index 000000000000..fb56559a3a3a
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-tl/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"Walang app."</string>
+ <string name="menu_show_system" msgid="906304605807554788">"Ipakita ang system"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"Itago ang system"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Pinapahintulutan"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Hindi pinapahintulutan"</string>
+ <string name="version_text" msgid="4001669804596458577">"bersyon <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-tr/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-tr/strings.xml
new file mode 100644
index 000000000000..5f5722b03899
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-tr/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"Uygulama yok"</string>
+ <string name="menu_show_system" msgid="906304605807554788">"Sistemi göster"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"Sistemi gizle"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"İzin veriliyor"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"İzin verilmiyor"</string>
+ <string name="version_text" msgid="4001669804596458577">"sürüm: <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-uk/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-uk/strings.xml
new file mode 100644
index 000000000000..a8632cae831b
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-uk/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"Додатків немає."</string>
+ <string name="menu_show_system" msgid="906304605807554788">"Показати системні додатки"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"Сховати системні додатки"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Дозволено"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Заборонено"</string>
+ <string name="version_text" msgid="4001669804596458577">"версія <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-ur/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-ur/strings.xml
new file mode 100644
index 000000000000..4b969bb247c2
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-ur/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"کوئی ایپ نہیں ہے۔"</string>
+ <string name="menu_show_system" msgid="906304605807554788">"سسٹم دکھائیں"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"سسٹم چھپائیں"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"اجازت ہے"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"اجازت نہیں ہے"</string>
+ <string name="version_text" msgid="4001669804596458577">"ورژن <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-uz/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-uz/strings.xml
new file mode 100644
index 000000000000..aed34e6579f6
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-uz/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"Hech narsa topilmadi."</string>
+ <string name="menu_show_system" msgid="906304605807554788">"Tizimga oid jarayonlar"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"Tizimga oid jarayonlarni berkitish"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Ruxsat berilgan"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Ruxsat berilmagan"</string>
+ <string name="version_text" msgid="4001669804596458577">"<xliff:g id="VERSION_NUM">%1$s</xliff:g> versiya"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-vi/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-vi/strings.xml
new file mode 100644
index 000000000000..75700c7f84b7
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-vi/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"Không có ứng dụng nào."</string>
+ <string name="menu_show_system" msgid="906304605807554788">"Hiện hệ thống"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"Ẩn hệ thống"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Được phép"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Không được phép"</string>
+ <string name="version_text" msgid="4001669804596458577">"phiên bản <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-zh-rCN/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-zh-rCN/strings.xml
new file mode 100644
index 000000000000..2c864cbb9b95
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-zh-rCN/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"没有应用。"</string>
+ <string name="menu_show_system" msgid="906304605807554788">"显示系统进程"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"隐藏系统进程"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"已允许"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"不允许"</string>
+ <string name="version_text" msgid="4001669804596458577">"版本 <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-zh-rHK/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-zh-rHK/strings.xml
new file mode 100644
index 000000000000..667a10ae2f77
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-zh-rHK/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"沒有應用程式。"</string>
+ <string name="menu_show_system" msgid="906304605807554788">"顯示系統程序"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"隱藏系統程序"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"允許"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"不允許"</string>
+ <string name="version_text" msgid="4001669804596458577">"版本 <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-zh-rTW/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-zh-rTW/strings.xml
new file mode 100644
index 000000000000..667a10ae2f77
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-zh-rTW/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"沒有應用程式。"</string>
+ <string name="menu_show_system" msgid="906304605807554788">"顯示系統程序"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"隱藏系統程序"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"允許"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"不允許"</string>
+ <string name="version_text" msgid="4001669804596458577">"版本 <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-zu/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-zu/strings.xml
new file mode 100644
index 000000000000..d3a614a6197a
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-zu/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"Awekho ama-app"</string>
+ <string name="menu_show_system" msgid="906304605807554788">"Bonisa isistimu"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"Fihla isistimu"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Kuvumelekile"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Akuvumelekile"</string>
+ <string name="version_text" msgid="4001669804596458577">"Uhlobo <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
index 487dbcb29927..4c144b294fa5 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
@@ -29,7 +29,7 @@ import kotlinx.coroutines.flow.combine
/**
* The config used to load the App List.
*/
-internal data class AppListConfig(
+data class AppListConfig(
val userId: Int,
val showInstantApps: Boolean,
)
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt
index 71cf23c61fa1..c08169e88852 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt
@@ -16,7 +16,6 @@
package com.android.settingslib.spaprivileged.model.app
-import android.app.AppOpsManager
import android.app.AppOpsManager.MODE_ALLOWED
import android.app.AppOpsManager.MODE_ERRORED
import android.app.AppOpsManager.Mode
@@ -25,34 +24,41 @@ import android.content.pm.ApplicationInfo
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.map
+import com.android.settingslib.spaprivileged.framework.common.appOpsManager
+
+interface IAppOpsController {
+ val mode: LiveData<Int>
+ val isAllowed: LiveData<Boolean>
+ get() = mode.map { it == MODE_ALLOWED }
+
+ fun setAllowed(allowed: Boolean)
+
+ @Mode
+ fun getMode(): Int
+}
class AppOpsController(
context: Context,
private val app: ApplicationInfo,
private val op: Int,
-) {
- private val appOpsManager = checkNotNull(context.getSystemService(AppOpsManager::class.java))
+) : IAppOpsController {
+ private val appOpsManager = context.appOpsManager
- val mode: LiveData<Int>
+ override val mode: LiveData<Int>
get() = _mode
- val isAllowed: LiveData<Boolean>
- get() = _mode.map { it == MODE_ALLOWED }
- fun setAllowed(allowed: Boolean) {
+ override fun setAllowed(allowed: Boolean) {
val mode = if (allowed) MODE_ALLOWED else MODE_ERRORED
appOpsManager.setMode(op, app.uid, app.packageName, mode)
_mode.postValue(mode)
}
@Mode
- fun getMode(): Int = appOpsManager.checkOpNoThrow(op, app.uid, app.packageName)
+ override fun getMode(): Int = appOpsManager.checkOpNoThrow(op, app.uid, app.packageName)
private val _mode = object : MutableLiveData<Int>() {
override fun onActive() {
postValue(getMode())
}
-
- override fun onInactive() {
- }
}
}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt
index a618c3d0575b..ae362c894e6d 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt
@@ -74,6 +74,8 @@ interface RestrictionsProvider {
fun restrictedModeState(): State<RestrictedMode?>
}
+typealias RestrictionsProviderFactory = (Context, Restrictions) -> RestrictionsProvider
+
internal class RestrictionsProviderImpl(
private val context: Context,
private val restrictions: Restrictions,
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfoPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfoPage.kt
index 8b19c5b0e319..0b45da67eaf7 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfoPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfoPage.kt
@@ -16,11 +16,12 @@
package com.android.settingslib.spaprivileged.template.app
+import android.content.pm.PackageInfo
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import com.android.settingslib.spa.widget.scaffold.RegularScaffold
import com.android.settingslib.spa.widget.ui.Footer
-import com.android.settingslib.spaprivileged.model.app.PackageManagers
+import com.android.settingslib.spaprivileged.model.app.IPackageManagers
@Composable
fun AppInfoPage(
@@ -28,18 +29,16 @@ fun AppInfoPage(
packageName: String,
userId: Int,
footerText: String,
- content: @Composable () -> Unit,
+ packageManagers: IPackageManagers,
+ content: @Composable PackageInfo.() -> Unit,
) {
+ val packageInfo = remember(packageName, userId) {
+ packageManagers.getPackageInfoAsUser(packageName, userId)
+ } ?: return
RegularScaffold(title = title) {
- val appInfoProvider = remember {
- PackageManagers.getPackageInfoAsUser(packageName, userId)?.let { packageInfo ->
- AppInfoProvider(packageInfo)
- }
- } ?: return@RegularScaffold
+ remember(packageInfo) { AppInfoProvider(packageInfo) }.AppInfo(displayVersion = true)
- appInfoProvider.AppInfo(displayVersion = true)
-
- content()
+ packageInfo.content()
Footer(footerText)
}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
index 15766e1b8e7c..2e0d85302b4c 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
@@ -49,13 +49,13 @@ import kotlinx.coroutines.Dispatchers
private const val TAG = "AppList"
private const val CONTENT_TYPE_HEADER = "header"
-internal data class AppListState(
+data class AppListState(
val showSystem: State<Boolean>,
val option: State<Int>,
val searchQuery: State<String>,
)
-internal data class AppListInput<T : AppRecord>(
+data class AppListInput<T : AppRecord>(
val config: AppListConfig,
val listModel: AppListModel<T>,
val state: AppListState,
@@ -70,10 +70,13 @@ internal data class AppListInput<T : AppRecord>(
* This UI element will take the remaining space on the screen to show the App List.
*/
@Composable
-internal fun <T : AppRecord> AppListInput<T>.AppList(
- appListDataSupplier: @Composable () -> State<AppListData<T>?> = {
- loadAppListData(config, listModel, state)
- },
+fun <T : AppRecord> AppListInput<T>.AppList() {
+ AppListImpl { loadAppListData(config, listModel, state) }
+}
+
+@Composable
+internal fun <T : AppRecord> AppListInput<T>.AppListImpl(
+ appListDataSupplier: @Composable () -> State<AppListData<T>?>,
) {
LogCompositions(TAG, config.userId.toString())
val appListData = appListDataSupplier()
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListItem.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListItem.kt
index 28bf8329a274..6d0d7d62bd5f 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListItem.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListItem.kt
@@ -28,7 +28,7 @@ import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
import com.android.settingslib.spaprivileged.model.app.AppRecord
-class AppListItemModel<T : AppRecord>(
+data class AppListItemModel<T : AppRecord>(
val record: T,
val label: String,
val summary: State<String>,
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
index d452c740d992..cb35fb0b27c7 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
@@ -47,24 +47,9 @@ fun <T : AppRecord> AppListPage(
primaryUserOnly: Boolean = false,
moreOptions: @Composable MoreOptionsScope.() -> Unit = {},
header: @Composable () -> Unit = {},
+ appList: @Composable AppListInput<T>.() -> Unit = { AppList() },
appItem: @Composable AppListItemModel<T>.() -> Unit,
) {
- AppListPageImpl(
- title, listModel, showInstantApps, primaryUserOnly, moreOptions, header, appItem,
- ) { it.AppList() }
-}
-
-@Composable
-internal fun <T : AppRecord> AppListPageImpl(
- title: String,
- listModel: AppListModel<T>,
- showInstantApps: Boolean = false,
- primaryUserOnly: Boolean = false,
- moreOptions: @Composable MoreOptionsScope.() -> Unit = {},
- header: @Composable () -> Unit = {},
- appItem: @Composable AppListItemModel<T>.() -> Unit,
- appList: @Composable (input: AppListInput<T>) -> Unit,
-) {
val showSystem = rememberSaveable { mutableStateOf(false) }
SearchScaffold(
title = title,
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt
index c6f41d3bd3e2..a3578321a098 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt
@@ -25,11 +25,12 @@ import androidx.compose.runtime.State
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.remember
+import com.android.settingslib.spa.framework.util.filterItem
import com.android.settingslib.spaprivileged.model.app.AppOpsController
import com.android.settingslib.spaprivileged.model.app.AppRecord
+import com.android.settingslib.spaprivileged.model.app.IAppOpsController
+import com.android.settingslib.spaprivileged.model.app.IPackageManagers
import com.android.settingslib.spaprivileged.model.app.PackageManagers
-import com.android.settingslib.spaprivileged.model.app.PackageManagers.hasGrantPermission
-import com.android.settingslib.spaprivileged.model.app.PackageManagers.hasRequestPermission
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
@@ -37,21 +38,24 @@ import kotlinx.coroutines.flow.map
data class AppOpPermissionRecord(
override val app: ApplicationInfo,
val hasRequestPermission: Boolean,
- var appOpsController: AppOpsController,
+ var appOpsController: IAppOpsController,
) : AppRecord
-abstract class AppOpPermissionListModel(private val context: Context) :
- TogglePermissionAppListModel<AppOpPermissionRecord> {
+abstract class AppOpPermissionListModel(
+ private val context: Context,
+ private val packageManagers: IPackageManagers = PackageManagers,
+) : TogglePermissionAppListModel<AppOpPermissionRecord> {
abstract val appOp: Int
abstract val permission: String
+ /** These not changeable packages will also be hidden from app list. */
private val notChangeablePackages =
setOf("android", "com.android.systemui", context.packageName)
override fun transform(userIdFlow: Flow<Int>, appListFlow: Flow<List<ApplicationInfo>>) =
userIdFlow.map { userId ->
- PackageManagers.getAppOpPermissionPackages(userId, permission)
+ packageManagers.getAppOpPermissionPackages(userId, permission)
}.combine(appListFlow) { packageNames, appList ->
appList.map { app ->
AppOpPermissionRecord(
@@ -64,14 +68,12 @@ abstract class AppOpPermissionListModel(private val context: Context) :
override fun transformItem(app: ApplicationInfo) = AppOpPermissionRecord(
app = app,
- hasRequestPermission = app.hasRequestPermission(permission),
+ hasRequestPermission = with(packageManagers) { app.hasRequestPermission(permission) },
appOpsController = AppOpsController(context = context, app = app, op = appOp),
)
override fun filter(userIdFlow: Flow<Int>, recordListFlow: Flow<List<AppOpPermissionRecord>>) =
- recordListFlow.map { recordList ->
- recordList.filter { it.hasRequestPermission }
- }
+ recordListFlow.filterItem(::isChangeable)
/**
* Defining the default behavior as permissible as long as the package requested this permission
@@ -85,7 +87,9 @@ abstract class AppOpPermissionListModel(private val context: Context) :
when (mode.value) {
null -> null
MODE_ALLOWED -> true
- MODE_DEFAULT -> record.app.hasGrantPermission(permission)
+ MODE_DEFAULT -> with(packageManagers) {
+ record.app.hasGrantPermission(permission)
+ }
else -> false
}
}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
index 8287693c90ee..5ae5adaff634 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
@@ -19,6 +19,7 @@ package com.android.settingslib.spaprivileged.template.app
import android.content.Context
import android.content.pm.ApplicationInfo
import android.os.Bundle
+import androidx.annotation.VisibleForTesting
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.State
@@ -38,23 +39,15 @@ import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
import com.android.settingslib.spaprivileged.model.app.AppRecord
+import com.android.settingslib.spaprivileged.model.app.IPackageManagers
import com.android.settingslib.spaprivileged.model.app.PackageManagers
import com.android.settingslib.spaprivileged.model.app.toRoute
import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
+import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderFactory
+import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderImpl
import com.android.settingslib.spaprivileged.template.preference.RestrictedSwitchPreference
import kotlinx.coroutines.Dispatchers
-private const val ENTRY_NAME = "AllowControl"
-private const val PERMISSION = "permission"
-private const val PACKAGE_NAME = "rt_packageName"
-private const val USER_ID = "rt_userId"
-private const val PAGE_NAME = "TogglePermissionAppInfoPage"
-private val PAGE_PARAMETER = listOf(
- navArgument(PERMISSION) { type = NavType.StringType },
- navArgument(PACKAGE_NAME) { type = NavType.StringType },
- navArgument(USER_ID) { type = NavType.IntType },
-)
-
internal class TogglePermissionAppInfoPageProvider(
private val appListTemplate: TogglePermissionAppListTemplate,
) : SettingsPageProvider {
@@ -64,11 +57,7 @@ internal class TogglePermissionAppInfoPageProvider(
override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
val owner = SettingsPage.create(name, parameter = parameter, arguments = arguments)
- val entryList = mutableListOf<SettingsEntry>()
- entryList.add(
- SettingsEntryBuilder.create(ENTRY_NAME, owner).build()
- )
- return entryList
+ return listOf(SettingsEntryBuilder.create("AllowControl", owner).build())
}
@Composable
@@ -76,11 +65,22 @@ internal class TogglePermissionAppInfoPageProvider(
val permissionType = arguments?.getString(PERMISSION)!!
val packageName = arguments.getString(PACKAGE_NAME)!!
val userId = arguments.getInt(USER_ID)
- val listModel = appListTemplate.rememberModel(permissionType)
- TogglePermissionAppInfoPage(listModel, packageName, userId)
+ appListTemplate.rememberModel(permissionType)
+ .TogglePermissionAppInfoPage(packageName, userId)
}
companion object {
+ private const val PAGE_NAME = "TogglePermissionAppInfoPage"
+ private const val PERMISSION = "permission"
+ private const val PACKAGE_NAME = "rt_packageName"
+ private const val USER_ID = "rt_userId"
+
+ private val PAGE_PARAMETER = listOf(
+ navArgument(PERMISSION) { type = NavType.StringType },
+ navArgument(PACKAGE_NAME) { type = NavType.StringType },
+ navArgument(USER_ID) { type = NavType.IntType },
+ )
+
@Composable
fun navigator(permissionType: String, app: ApplicationInfo) =
navigator(route = "$PAGE_NAME/$permissionType/${app.toRoute()}")
@@ -116,43 +116,36 @@ internal class TogglePermissionAppInfoPageProvider(
}
}
+@VisibleForTesting
@Composable
-private fun TogglePermissionAppInfoPage(
- listModel: TogglePermissionAppListModel<out AppRecord>,
+internal fun TogglePermissionAppListModel<out AppRecord>.TogglePermissionAppInfoPage(
packageName: String,
userId: Int,
+ packageManagers: IPackageManagers = PackageManagers,
+ restrictionsProviderFactory: RestrictionsProviderFactory = ::RestrictionsProviderImpl,
) {
AppInfoPage(
- title = stringResource(listModel.pageTitleResId),
+ title = stringResource(pageTitleResId),
packageName = packageName,
userId = userId,
- footerText = stringResource(listModel.footerResId),
+ footerText = stringResource(footerResId),
+ packageManagers = packageManagers,
) {
- val model = createSwitchModel(listModel, packageName, userId) ?: return@AppInfoPage
- LaunchedEffect(model, Dispatchers.Default) {
- model.initState()
- }
- RestrictedSwitchPreference(model, Restrictions(userId, listModel.switchRestrictionKeys))
+ val model = createSwitchModel(applicationInfo)
+ val restrictions = Restrictions(userId, switchRestrictionKeys)
+ RestrictedSwitchPreference(model, restrictions, restrictionsProviderFactory)
}
}
@Composable
-private fun <T : AppRecord> createSwitchModel(
- listModel: TogglePermissionAppListModel<T>,
- packageName: String,
- userId: Int,
-): TogglePermissionSwitchModel<T>? {
- val record = remember {
- PackageManagers.getApplicationInfoAsUser(packageName, userId)?.let { app ->
- listModel.transformItem(app)
- }
- } ?: return null
-
+private fun <T : AppRecord> TogglePermissionAppListModel<T>.createSwitchModel(
+ app: ApplicationInfo,
+): TogglePermissionSwitchModel<T> {
val context = LocalContext.current
- val isAllowed = listModel.isAllowed(record)
- return remember {
- TogglePermissionSwitchModel(context, listModel, record, isAllowed)
- }
+ val record = remember(app) { transformItem(app) }
+ val isAllowed = isAllowed(record)
+ return remember(record) { TogglePermissionSwitchModel(context, this, record, isAllowed) }
+ .also { model -> LaunchedEffect(model, Dispatchers.IO) { model.initState() } }
}
private class TogglePermissionSwitchModel<T : AppRecord>(
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt
index a003da81e06d..b08b6dfa67a6 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt
@@ -38,19 +38,14 @@ import com.android.settingslib.spaprivileged.model.enterprise.BlockedByAdmin
import com.android.settingslib.spaprivileged.model.enterprise.NoRestricted
import com.android.settingslib.spaprivileged.model.enterprise.RestrictedMode
import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
-import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProvider
+import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderFactory
import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderImpl
@Composable
-fun RestrictedSwitchPreference(model: SwitchPreferenceModel, restrictions: Restrictions) {
- RestrictedSwitchPreferenceImpl(model, restrictions, ::RestrictionsProviderImpl)
-}
-
-@Composable
-internal fun RestrictedSwitchPreferenceImpl(
+fun RestrictedSwitchPreference(
model: SwitchPreferenceModel,
restrictions: Restrictions,
- restrictionsProviderFactory: (Context, Restrictions) -> RestrictionsProvider,
+ restrictionsProviderFactory: RestrictionsProviderFactory = ::RestrictionsProviderImpl,
) {
if (restrictions.keys.isEmpty()) {
SwitchPreference(model)
diff --git a/packages/SettingsLib/SpaPrivileged/tests/AndroidManifest.xml b/packages/SettingsLib/SpaPrivileged/tests/AndroidManifest.xml
index c4f490ed398b..8d384e8ca02e 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/AndroidManifest.xml
+++ b/packages/SettingsLib/SpaPrivileged/tests/AndroidManifest.xml
@@ -15,7 +15,7 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.settingslib.spaprivileged.tests">
+ package="com.android.settingslib.spaprivileged.test">
<application>
<uses-library android:name="android.test.runner" />
@@ -24,5 +24,5 @@
<instrumentation
android:name="androidx.test.runner.AndroidJUnitRunner"
android:label="Tests for SpaPrivilegedLib"
- android:targetPackage="com.android.settingslib.spaprivileged.tests" />
+ android:targetPackage="com.android.settingslib.spaprivileged.test" />
</manifest>
diff --git a/packages/SettingsLib/SpaPrivileged/tests/res/values/strings.xml b/packages/SettingsLib/SpaPrivileged/tests/res/values/strings.xml
index fb1e09acddf5..bdc0ba8224de 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/res/values/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/tests/res/values/strings.xml
@@ -22,5 +22,14 @@
<string name="test_permission_switch_title" translatable="false">Allow Test Permission</string>
<!-- Test Permission footer. [DO NOT TRANSLATE] -->
- <string name="test_permission_footer" translatable="false">Test Permission is for demo.</string>
+ <string name="test_permission_footer" translatable="false">Test Permission is for testing.</string>
+
+ <!-- Test App Op Permission title. [DO NOT TRANSLATE] -->
+ <string name="test_app_op_permission_title" translatable="false">Test App Op Permission</string>
+
+ <!-- Test App Op Permission switch title. [DO NOT TRANSLATE] -->
+ <string name="test_app_op_permission_switch_title" translatable="false">Allow Test App Op Permission</string>
+
+ <!-- Test App Op Permission footer. [DO NOT TRANSLATE] -->
+ <string name="test_app_op_permission_footer" translatable="false">Test App Op Permission is for testing.</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/compose/DisposableBroadcastReceiverAsUserTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/compose/DisposableBroadcastReceiverAsUserTest.kt
new file mode 100644
index 000000000000..01f4cc67de83
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/compose/DisposableBroadcastReceiverAsUserTest.kt
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spaprivileged.framework.compose
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.os.UserHandle
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+import org.mockito.Mockito.`when` as whenever
+
+@RunWith(AndroidJUnit4::class)
+class DisposableBroadcastReceiverAsUserTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ @get:Rule
+ val mockito: MockitoRule = MockitoJUnit.rule()
+
+ @Mock
+ private lateinit var context: Context
+
+ private var registeredBroadcastReceiver: BroadcastReceiver? = null
+
+ @Before
+ fun setUp() {
+ whenever(context.registerReceiverAsUser(any(), any(), any(), any(), any()))
+ .thenAnswer {
+ registeredBroadcastReceiver = it.arguments[0] as BroadcastReceiver
+ null
+ }
+ }
+
+ @Test
+ fun broadcastReceiver_registered() {
+ composeTestRule.setContent {
+ CompositionLocalProvider(LocalContext provides context) {
+ DisposableBroadcastReceiverAsUser(IntentFilter(), USER_HANDLE) {}
+ }
+ }
+
+ assertThat(registeredBroadcastReceiver).isNotNull()
+ }
+
+ @Test
+ fun broadcastReceiver_isCalledOnReceive() {
+ var onReceiveIsCalled = false
+ composeTestRule.setContent {
+ CompositionLocalProvider(LocalContext provides context) {
+ DisposableBroadcastReceiverAsUser(IntentFilter(), USER_HANDLE) {
+ onReceiveIsCalled = true
+ }
+ }
+ }
+
+ registeredBroadcastReceiver!!.onReceive(context, Intent())
+
+ assertThat(onReceiveIsCalled).isTrue()
+ }
+
+ @Test
+ fun broadcastReceiver_onStartIsCalled() {
+ var onStartIsCalled = false
+ composeTestRule.setContent {
+ CompositionLocalProvider(LocalContext provides context) {
+ DisposableBroadcastReceiverAsUser(
+ intentFilter = IntentFilter(),
+ userHandle = USER_HANDLE,
+ onStart = { onStartIsCalled = true },
+ onReceive = {},
+ )
+ }
+ }
+
+ assertThat(onStartIsCalled).isTrue()
+ }
+
+ private companion object {
+ val USER_HANDLE: UserHandle = UserHandle.of(0)
+ }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppInfoTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppInfoTest.kt
index 8ca7950905af..bb56c10b28e3 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppInfoTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppInfoTest.kt
@@ -36,7 +36,7 @@ class AppInfoTest {
@get:Rule
val composeTestRule = createComposeRule()
- private var context: Context = ApplicationProvider.getApplicationContext()
+ private val context: Context = ApplicationProvider.getApplicationContext()
@Test
fun appInfoLabel_isDisplayed() {
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListPageTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListPageTest.kt
index c3c96c645a15..f2267f61847c 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListPageTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListPageTest.kt
@@ -41,7 +41,7 @@ class AppListPageTest {
@get:Rule
val composeTestRule = createComposeRule()
- private var context: Context = ApplicationProvider.getApplicationContext()
+ private val context: Context = ApplicationProvider.getApplicationContext()
@Test
fun title_isDisplayed() {
@@ -118,12 +118,12 @@ class AppListPageTest {
): State<AppListInput<TestAppRecord>?> {
val appListState = mutableStateOf<AppListInput<TestAppRecord>?>(null)
composeTestRule.setContent {
- AppListPageImpl(
+ AppListPage(
title = TITLE,
listModel = TestAppListModel(options),
header = header,
appItem = { AppListItem {} },
- appList = { appListState.value = it },
+ appList = { appListState.value = this },
)
}
return appListState
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListSwitchItemTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListSwitchItemTest.kt
new file mode 100644
index 000000000000..abdcd3bc5527
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListSwitchItemTest.kt
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spaprivileged.template.app
+
+import android.content.pm.ApplicationInfo
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsEnabled
+import androidx.compose.ui.test.assertIsNotEnabled
+import androidx.compose.ui.test.assertIsOff
+import androidx.compose.ui.test.assertIsOn
+import androidx.compose.ui.test.isToggleable
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.framework.compose.stateOf
+import com.android.settingslib.spaprivileged.model.app.AppRecord
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class AppListSwitchItemTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ @Test
+ fun appLabel_displayed() {
+ composeTestRule.setContent {
+ ITEM_MODEL.AppListSwitchItem(
+ onClick = {},
+ checked = stateOf(null),
+ changeable = stateOf(false),
+ onCheckedChange = {},
+ )
+ }
+
+ composeTestRule.onNodeWithText(LABEL).assertIsDisplayed()
+ }
+
+ @Test
+ fun summary_displayed() {
+ composeTestRule.setContent {
+ ITEM_MODEL.AppListSwitchItem(
+ onClick = {},
+ checked = stateOf(null),
+ changeable = stateOf(false),
+ onCheckedChange = {},
+ )
+ }
+
+ composeTestRule.onNodeWithText(SUMMARY).assertIsDisplayed()
+ }
+
+ @Test
+ fun title_onClick() {
+ var titleClicked = false
+ composeTestRule.setContent {
+ ITEM_MODEL.AppListSwitchItem(
+ onClick = { titleClicked = true },
+ checked = stateOf(false),
+ changeable = stateOf(false),
+ onCheckedChange = {},
+ )
+ }
+
+ composeTestRule.onNodeWithText(LABEL).performClick()
+
+ assertThat(titleClicked).isTrue()
+ }
+
+ @Test
+ fun switch_checkIsNull() {
+ composeTestRule.setContent {
+ ITEM_MODEL.AppListSwitchItem(
+ onClick = {},
+ checked = stateOf(null),
+ changeable = stateOf(false),
+ onCheckedChange = {},
+ )
+ }
+
+ composeTestRule.onNode(isToggleable()).assertDoesNotExist()
+ }
+
+ @Test
+ fun switch_checked() {
+ composeTestRule.setContent {
+ ITEM_MODEL.AppListSwitchItem(
+ onClick = {},
+ checked = stateOf(true),
+ changeable = stateOf(false),
+ onCheckedChange = {},
+ )
+ }
+
+ composeTestRule.onNode(isToggleable()).assertIsOn()
+ }
+
+ @Test
+ fun switch_notChecked() {
+ composeTestRule.setContent {
+ ITEM_MODEL.AppListSwitchItem(
+ onClick = {},
+ checked = stateOf(false),
+ changeable = stateOf(false),
+ onCheckedChange = {},
+ )
+ }
+
+ composeTestRule.onNode(isToggleable()).assertIsOff()
+ }
+
+ @Test
+ fun switch_changeable() {
+ composeTestRule.setContent {
+ ITEM_MODEL.AppListSwitchItem(
+ onClick = {},
+ checked = stateOf(false),
+ changeable = stateOf(true),
+ onCheckedChange = {},
+ )
+ }
+
+ composeTestRule.onNode(isToggleable()).assertIsEnabled()
+ }
+
+ @Test
+ fun switch_notChangeable() {
+ composeTestRule.setContent {
+ ITEM_MODEL.AppListSwitchItem(
+ onClick = {},
+ checked = stateOf(false),
+ changeable = stateOf(false),
+ onCheckedChange = {},
+ )
+ }
+
+ composeTestRule.onNode(isToggleable()).assertIsNotEnabled()
+ }
+
+ @Test
+ fun switch_onClick() {
+ var switchClicked = false
+ composeTestRule.setContent {
+ ITEM_MODEL.AppListSwitchItem(
+ onClick = {},
+ checked = stateOf(false),
+ changeable = stateOf(true),
+ onCheckedChange = { switchClicked = true },
+ )
+ }
+
+ composeTestRule.onNode(isToggleable()).performClick()
+
+ assertThat(switchClicked).isTrue()
+ }
+
+ private companion object {
+ const val PACKAGE_NAME = "package.name"
+ const val LABEL = "Label"
+ const val SUMMARY = "Summary"
+ val APP = ApplicationInfo().apply {
+ packageName = PACKAGE_NAME
+ }
+ val ITEM_MODEL = AppListItemModel(
+ record = object : AppRecord {
+ override val app = APP
+ },
+ label = LABEL,
+ summary = stateOf(SUMMARY),
+ )
+ }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt
index df80dd4007da..267766992d2c 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt
@@ -44,7 +44,7 @@ class AppListTest {
@get:Rule
val composeTestRule = createComposeRule()
- private var context: Context = ApplicationProvider.getApplicationContext()
+ private val context: Context = ApplicationProvider.getApplicationContext()
@Test
fun whenNoApps() {
@@ -102,7 +102,7 @@ class AppListTest {
appItem = { AppListItem {} },
bottomPadding = 0.dp,
)
- appListInput.AppList { stateOf(AppListData(appEntries, option = 0)) }
+ appListInput.AppListImpl { stateOf(AppListData(appEntries, option = 0)) }
}
}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt
new file mode 100644
index 000000000000..f1d9abe86804
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt
@@ -0,0 +1,264 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spaprivileged.template.app
+
+import android.app.AppOpsManager
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import androidx.compose.runtime.State
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.lifecycle.liveData
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull
+import com.android.settingslib.spaprivileged.model.app.IAppOpsController
+import com.android.settingslib.spaprivileged.model.app.IPackageManagers
+import com.android.settingslib.spaprivileged.test.R
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.runTest
+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
+import org.mockito.Mockito.`when` as whenever
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidJUnit4::class)
+class AppOpPermissionAppListTest {
+ @get:Rule
+ val mockito: MockitoRule = MockitoJUnit.rule()
+
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ private val context: Context = ApplicationProvider.getApplicationContext()
+
+ @Mock
+ private lateinit var packageManagers: IPackageManagers
+
+ private lateinit var listModel: TestAppOpPermissionAppListModel
+
+ @Before
+ fun setUp() = runTest {
+ whenever(packageManagers.getAppOpPermissionPackages(USER_ID, PERMISSION))
+ .thenReturn(emptySet())
+ listModel = TestAppOpPermissionAppListModel()
+ }
+
+ @Test
+ fun transformItem_recordHasCorrectApp() {
+ val record = listModel.transformItem(APP)
+
+ assertThat(record.app).isSameInstanceAs(APP)
+ }
+
+ @Test
+ fun transformItem_hasRequestPermission() = runTest {
+ with(packageManagers) {
+ whenever(APP.hasRequestPermission(PERMISSION)).thenReturn(true)
+ }
+
+ val record = listModel.transformItem(APP)
+
+ assertThat(record.hasRequestPermission).isTrue()
+ }
+
+ @Test
+ fun transformItem_notRequestPermission() = runTest {
+ with(packageManagers) {
+ whenever(APP.hasRequestPermission(PERMISSION)).thenReturn(false)
+ }
+
+ val record = listModel.transformItem(APP)
+
+ assertThat(record.hasRequestPermission).isFalse()
+ }
+
+ @Test
+ fun filter() = runTest {
+ with(packageManagers) {
+ whenever(APP.hasRequestPermission(PERMISSION)).thenReturn(false)
+ }
+ val record = AppOpPermissionRecord(
+ app = APP,
+ hasRequestPermission = false,
+ appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT),
+ )
+
+ val recordListFlow = listModel.filter(flowOf(USER_ID), flowOf(listOf(record)))
+
+ val recordList = recordListFlow.firstWithTimeoutOrNull()!!
+ assertThat(recordList).isEmpty()
+ }
+
+ @Test
+ fun isAllowed_allowed() {
+ val record = AppOpPermissionRecord(
+ app = APP,
+ hasRequestPermission = true,
+ appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_ALLOWED),
+ )
+
+ val isAllowed = getIsAllowed(record)
+
+ assertThat(isAllowed).isTrue()
+ }
+
+ @Test
+ fun isAllowed_defaultAndHasGrantPermission() {
+ with(packageManagers) {
+ whenever(APP.hasGrantPermission(PERMISSION)).thenReturn(true)
+ }
+ val record = AppOpPermissionRecord(
+ app = APP,
+ hasRequestPermission = true,
+ appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT),
+ )
+
+ val isAllowed = getIsAllowed(record)
+
+ assertThat(isAllowed).isTrue()
+ }
+
+ @Test
+ fun isAllowed_defaultAndNotGrantPermission() {
+ with(packageManagers) {
+ whenever(APP.hasGrantPermission(PERMISSION)).thenReturn(false)
+ }
+ val record = AppOpPermissionRecord(
+ app = APP,
+ hasRequestPermission = true,
+ appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT),
+ )
+
+ val isAllowed = getIsAllowed(record)
+
+ assertThat(isAllowed).isFalse()
+ }
+
+ @Test
+ fun isAllowed_notAllowed() {
+ val record = AppOpPermissionRecord(
+ app = APP,
+ hasRequestPermission = true,
+ appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_ERRORED),
+ )
+
+ val isAllowed = getIsAllowed(record)
+
+ assertThat(isAllowed).isFalse()
+ }
+
+ @Test
+ fun isChangeable_notRequestPermission() {
+ val record = AppOpPermissionRecord(
+ app = APP,
+ hasRequestPermission = false,
+ appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT),
+ )
+
+ val isChangeable = listModel.isChangeable(record)
+
+ assertThat(isChangeable).isFalse()
+ }
+
+ @Test
+ fun isChangeable_notChangeablePackages() {
+ val record = AppOpPermissionRecord(
+ app = NOT_CHANGEABLE_APP,
+ hasRequestPermission = true,
+ appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT),
+ )
+
+ val isChangeable = listModel.isChangeable(record)
+
+ assertThat(isChangeable).isFalse()
+ }
+
+ @Test
+ fun isChangeable_hasRequestPermissionAndChangeable() {
+ val record = AppOpPermissionRecord(
+ app = APP,
+ hasRequestPermission = true,
+ appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT),
+ )
+
+ val isChangeable = listModel.isChangeable(record)
+
+ assertThat(isChangeable).isTrue()
+ }
+
+ @Test
+ fun setAllowed() {
+ val appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT)
+ val record = AppOpPermissionRecord(
+ app = APP,
+ hasRequestPermission = true,
+ appOpsController = appOpsController,
+ )
+
+ listModel.setAllowed(record = record, newAllowed = true)
+
+ assertThat(appOpsController.setAllowedCalledWith).isTrue()
+ }
+
+ private fun getIsAllowed(record: AppOpPermissionRecord): Boolean? {
+ lateinit var isAllowedState: State<Boolean?>
+ composeTestRule.setContent {
+ isAllowedState = listModel.isAllowed(record)
+ }
+ return isAllowedState.value
+ }
+
+ private inner class TestAppOpPermissionAppListModel :
+ AppOpPermissionListModel(context, packageManagers) {
+ override val pageTitleResId = R.string.test_app_op_permission_title
+ override val switchTitleResId = R.string.test_app_op_permission_switch_title
+ override val footerResId = R.string.test_app_op_permission_footer
+ override val appOp = AppOpsManager.OP_MANAGE_MEDIA
+ override val permission = PERMISSION
+ }
+
+ private companion object {
+ const val USER_ID = 0
+ const val PACKAGE_NAME = "package.name"
+ const val PERMISSION = "PERMISSION"
+ val APP = ApplicationInfo().apply {
+ packageName = PACKAGE_NAME
+ }
+ val NOT_CHANGEABLE_APP = ApplicationInfo().apply {
+ packageName = "android"
+ }
+ }
+}
+
+private class FakeAppOpsController(private val fakeMode: Int) : IAppOpsController {
+ var setAllowedCalledWith: Boolean? = null
+
+ override val mode = liveData { emit(fakeMode) }
+
+ override fun setAllowed(allowed: Boolean) {
+ setAllowedCalledWith = allowed
+ }
+
+ override fun getMode() = fakeMode
+}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppStorageSizeTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppStorageSizeTest.kt
index 8e98d8cd6975..066e28a4d008 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppStorageSizeTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppStorageSizeTest.kt
@@ -48,7 +48,7 @@ class AppStorageSizeTest {
val composeTestRule = createComposeRule()
@Spy
- private var context: Context = ApplicationProvider.getApplicationContext()
+ private val context: Context = ApplicationProvider.getApplicationContext()
@Mock
private lateinit var storageStatsManager: StorageStatsManager
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPageTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPageTest.kt
new file mode 100644
index 000000000000..ecad08a0aca2
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPageTest.kt
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spaprivileged.template.app
+
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageInfo
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsNotEnabled
+import androidx.compose.ui.test.assertIsOff
+import androidx.compose.ui.test.assertIsOn
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spaprivileged.model.app.IPackageManagers
+import com.android.settingslib.spaprivileged.model.enterprise.NoRestricted
+import com.android.settingslib.spaprivileged.tests.testutils.FakeRestrictionsProvider
+import com.android.settingslib.spaprivileged.tests.testutils.TestTogglePermissionAppListModel
+import com.android.settingslib.spaprivileged.tests.testutils.TestTogglePermissionAppListProvider
+import com.google.common.truth.Truth.assertThat
+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
+import org.mockito.Mockito.`when` as whenever
+
+@RunWith(AndroidJUnit4::class)
+class TogglePermissionAppInfoPageTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ @get:Rule
+ val mockito: MockitoRule = MockitoJUnit.rule()
+
+ private val context: Context = ApplicationProvider.getApplicationContext()
+
+ @Mock
+ private lateinit var packageManagers: IPackageManagers
+
+ private val fakeRestrictionsProvider = FakeRestrictionsProvider()
+
+ private val appListTemplate =
+ TogglePermissionAppListTemplate(listOf(TestTogglePermissionAppListProvider))
+
+ private val appInfoPageProvider = TogglePermissionAppInfoPageProvider(appListTemplate)
+
+ @Before
+ fun setUp() {
+ fakeRestrictionsProvider.restrictedMode = NoRestricted
+ whenever(packageManagers.getPackageInfoAsUser(PACKAGE_NAME, USER_ID))
+ .thenReturn(PACKAGE_INFO)
+ }
+
+ @Test
+ fun buildEntry() {
+ val entryList = appInfoPageProvider.buildEntry(null)
+
+ assertThat(entryList).hasSize(1)
+ assertThat(entryList[0].displayName).isEqualTo("AllowControl")
+ }
+
+ @Test
+ fun title_isDisplayed() {
+ val listModel = TestTogglePermissionAppListModel()
+
+ setTogglePermissionAppInfoPage(listModel)
+
+ composeTestRule.onNodeWithText(context.getString(listModel.pageTitleResId))
+ .assertIsDisplayed()
+ }
+
+ @Test
+ fun whenAllowed_switchIsOn() {
+ val listModel = TestTogglePermissionAppListModel(isAllowed = true)
+
+ setTogglePermissionAppInfoPage(listModel)
+
+ composeTestRule.onNodeWithText(context.getString(listModel.switchTitleResId))
+ .assertIsOn()
+ }
+
+ @Test
+ fun whenNotAllowed_switchIsOff() {
+ val listModel = TestTogglePermissionAppListModel(isAllowed = false)
+
+ setTogglePermissionAppInfoPage(listModel)
+
+ composeTestRule.onNodeWithText(context.getString(listModel.switchTitleResId))
+ .assertIsOff()
+ }
+
+ @Test
+ fun whenNotChangeable_switchNotEnabled() {
+ val listModel = TestTogglePermissionAppListModel(isAllowed = false, isChangeable = false)
+
+ setTogglePermissionAppInfoPage(listModel)
+
+ composeTestRule.onNodeWithText(context.getString(listModel.switchTitleResId))
+ .assertIsDisplayed()
+ .assertIsNotEnabled()
+ }
+
+ @Test
+ fun footer_isDisplayed() {
+ val listModel = TestTogglePermissionAppListModel()
+
+ setTogglePermissionAppInfoPage(listModel)
+
+ composeTestRule.onNodeWithText(context.getString(listModel.footerResId))
+ .assertIsDisplayed()
+ }
+
+ private fun setTogglePermissionAppInfoPage(listModel: TestTogglePermissionAppListModel) {
+ composeTestRule.setContent {
+ listModel.TogglePermissionAppInfoPage(
+ packageName = PACKAGE_NAME,
+ userId = USER_ID,
+ packageManagers = packageManagers,
+ restrictionsProviderFactory = { _, _ -> fakeRestrictionsProvider },
+ )
+ }
+ }
+
+ private companion object {
+ const val USER_ID = 0
+ const val PACKAGE_NAME = "package.name"
+ val APP = ApplicationInfo().apply {
+ packageName = PACKAGE_NAME
+ }
+ val PACKAGE_INFO = PackageInfo().apply {
+ packageName = PACKAGE_NAME
+ applicationInfo = APP
+ }
+ }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt
index 4bc612abb1a4..355dfb6b3ae1 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt
@@ -24,7 +24,7 @@ import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.android.settingslib.spaprivileged.R
+import com.android.settingslib.spaprivileged.test.R
import com.android.settingslib.spaprivileged.tests.testutils.TestTogglePermissionAppListModel
import com.google.common.truth.Truth.assertThat
import org.junit.Rule
@@ -36,7 +36,7 @@ class TogglePermissionAppListPageTest {
@get:Rule
val composeTestRule = createComposeRule()
- private var context: Context = ApplicationProvider.getApplicationContext()
+ private val context: Context = ApplicationProvider.getApplicationContext()
@Test
fun appListInjectEntry_titleDisplayed() {
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListTest.kt
index af3189f2ec41..1818f2d92f9c 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListTest.kt
@@ -24,8 +24,8 @@ import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.android.settingslib.spaprivileged.R
-import com.android.settingslib.spaprivileged.tests.testutils.TestTogglePermissionAppListModel
+import com.android.settingslib.spaprivileged.test.R
+import com.android.settingslib.spaprivileged.tests.testutils.TestTogglePermissionAppListProvider
import com.google.common.truth.Truth.assertThat
import org.junit.Rule
import org.junit.Test
@@ -36,7 +36,7 @@ class TogglePermissionAppListTest {
@get:Rule
val composeTestRule = createComposeRule()
- private var context: Context = ApplicationProvider.getApplicationContext()
+ private val context: Context = ApplicationProvider.getApplicationContext()
@Test
fun appListInjectEntry_titleDisplayed() {
@@ -70,8 +70,3 @@ class TogglePermissionAppListTest {
assertThat(createPageProviders.any { it is TogglePermissionAppInfoPageProvider }).isTrue()
}
}
-
-private object TestTogglePermissionAppListProvider : TogglePermissionAppListProvider {
- override val permissionType = "test.PERMISSION"
- override fun createModel(context: Context) = TestTogglePermissionAppListModel()
-}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceTest.kt
index 7f57025c5fe5..a13c4835b5d5 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceTest.kt
@@ -142,7 +142,7 @@ class RestrictedSwitchPreferenceTest {
private fun setContent(restrictions: Restrictions) {
composeTestRule.setContent {
- RestrictedSwitchPreferenceImpl(switchPreferenceModel, restrictions) { _, _ ->
+ RestrictedSwitchPreference(switchPreferenceModel, restrictions) { _, _ ->
fakeRestrictionsProvider
}
}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestTogglePermissionAppListModel.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestTogglePermissionAppListModel.kt
index 91a9c6b620af..b13fbb3e8e41 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestTogglePermissionAppListModel.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestTogglePermissionAppListModel.kt
@@ -19,11 +19,14 @@ package com.android.settingslib.spaprivileged.tests.testutils
import android.content.pm.ApplicationInfo
import androidx.compose.runtime.Composable
import com.android.settingslib.spa.framework.compose.stateOf
-import com.android.settingslib.spaprivileged.R
+import com.android.settingslib.spaprivileged.test.R
import com.android.settingslib.spaprivileged.template.app.TogglePermissionAppListModel
import kotlinx.coroutines.flow.Flow
-class TestTogglePermissionAppListModel : TogglePermissionAppListModel<TestAppRecord> {
+class TestTogglePermissionAppListModel(
+ private val isAllowed: Boolean? = null,
+ private val isChangeable: Boolean = false,
+) : TogglePermissionAppListModel<TestAppRecord> {
override val pageTitleResId = R.string.test_permission_title
override val switchTitleResId = R.string.test_permission_switch_title
override val footerResId = R.string.test_permission_footer
@@ -34,9 +37,9 @@ class TestTogglePermissionAppListModel : TogglePermissionAppListModel<TestAppRec
recordListFlow
@Composable
- override fun isAllowed(record: TestAppRecord) = stateOf(null)
+ override fun isAllowed(record: TestAppRecord) = stateOf(isAllowed)
- override fun isChangeable(record: TestAppRecord) = false
+ override fun isChangeable(record: TestAppRecord) = isChangeable
override fun setAllowed(record: TestAppRecord, newAllowed: Boolean) {}
}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestTogglePermissionAppListProvider.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestTogglePermissionAppListProvider.kt
new file mode 100644
index 000000000000..354bbf536a85
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestTogglePermissionAppListProvider.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spaprivileged.tests.testutils
+
+import android.content.Context
+import com.android.settingslib.spaprivileged.template.app.TogglePermissionAppListProvider
+
+object TestTogglePermissionAppListProvider : TogglePermissionAppListProvider {
+ override val permissionType = "test.PERMISSION"
+ override fun createModel(context: Context) = TestTogglePermissionAppListModel()
+}
diff --git a/packages/SettingsLib/res/values-af/strings.xml b/packages/SettingsLib/res/values-af/strings.xml
index a39a2b9cc12a..023433044ac0 100644
--- a/packages/SettingsLib/res/values-af/strings.xml
+++ b/packages/SettingsLib/res/values-af/strings.xml
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> oor tot vol"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> oor tot vol"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> – Laaiproses is onderbreek"</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Onbekend"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Laai"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Laai tans vinnig"</string>
diff --git a/packages/SettingsLib/res/values-am/strings.xml b/packages/SettingsLib/res/values-am/strings.xml
index d9536787ab53..29b188c9fed4 100644
--- a/packages/SettingsLib/res/values-am/strings.xml
+++ b/packages/SettingsLib/res/values-am/strings.xml
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"እስኪሞላ ድረስ <xliff:g id="TIME">%1$s</xliff:g> ይቀራል"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - እስኪሞላ ድረስ <xliff:g id="TIME">%2$s</xliff:g> ይቀራል"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - ኃይል መሙላት ባለበት ቆሟል"</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"ያልታወቀ"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"ኃይል በመሙላት ላይ"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"ኃይል በፍጥነት በመሙላት ላይ"</string>
diff --git a/packages/SettingsLib/res/values-ar/strings.xml b/packages/SettingsLib/res/values-ar/strings.xml
index 3b00d26fe793..28a3f1872546 100644
--- a/packages/SettingsLib/res/values-ar/strings.xml
+++ b/packages/SettingsLib/res/values-ar/strings.xml
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"يتبقّى <xliff:g id="TIME">%1$s</xliff:g> حتى اكتمال شحن البطارية."</string>
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - يتبقّى <xliff:g id="TIME">%2$s</xliff:g> حتى اكتمال شحن البطارية."</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - تم إيقاف الشحن مؤقتًا"</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"غير معروف"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"جارٍ الشحن"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"جارٍ الشحن سريعًا"</string>
diff --git a/packages/SettingsLib/res/values-as/strings.xml b/packages/SettingsLib/res/values-as/strings.xml
index 0acd7ee63976..fc54e04eb5f6 100644
--- a/packages/SettingsLib/res/values-as/strings.xml
+++ b/packages/SettingsLib/res/values-as/strings.xml
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"সম্পূৰ্ণ হ’বলৈ <xliff:g id="TIME">%1$s</xliff:g> বাকী আছে"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"সম্পূৰ্ণ হ’বলৈ <xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> বাকী আছে"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - চাৰ্জিং পজ কৰা হৈছে"</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"অজ্ঞাত"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"চাৰ্জ কৰি থকা হৈছে"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"দ্ৰুততাৰে চাৰ্জ হৈছে"</string>
diff --git a/packages/SettingsLib/res/values-az/strings.xml b/packages/SettingsLib/res/values-az/strings.xml
index 33efb4fdeb98..e2e294f17e1d 100644
--- a/packages/SettingsLib/res/values-az/strings.xml
+++ b/packages/SettingsLib/res/values-az/strings.xml
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Tam şarj edilənədək <xliff:g id="TIME">%1$s</xliff:g> qalıb"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - tam şarj edilənədək <xliff:g id="TIME">%2$s</xliff:g> qalıb"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Şarj durdurulub"</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Naməlum"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Enerji doldurma"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Sürətlə doldurulur"</string>
diff --git a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
index 108a1365421f..479c9ca6d4dd 100644
--- a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
+++ b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> do kraja punjenja"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> do kraja punjenja"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> – Punjenje je zaustavljeno"</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Nepoznato"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Puni se"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Brzo se puni"</string>
diff --git a/packages/SettingsLib/res/values-be/strings.xml b/packages/SettingsLib/res/values-be/strings.xml
index 5a7020216594..b45996cc1644 100644
--- a/packages/SettingsLib/res/values-be/strings.xml
+++ b/packages/SettingsLib/res/values-be/strings.xml
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Да поўнай зарадкі засталося <xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – да поўнай зарадкі засталося: <xliff:g id="TIME">%2$s</xliff:g>"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Зарадка прыпынена"</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Невядома"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Зарадка"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Хуткая зарадка"</string>
diff --git a/packages/SettingsLib/res/values-bg/strings.xml b/packages/SettingsLib/res/values-bg/strings.xml
index 7e97dd366bb8..ecb520866e07 100644
--- a/packages/SettingsLib/res/values-bg/strings.xml
+++ b/packages/SettingsLib/res/values-bg/strings.xml
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Оставащо време до пълно зареждане: <xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – Оставащо време до пълно зареждане: <xliff:g id="TIME">%2$s</xliff:g>"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g>: Зареждането е на пауза"</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Неизвестно"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Зарежда се"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Зарежда се бързо"</string>
diff --git a/packages/SettingsLib/res/values-bn/strings.xml b/packages/SettingsLib/res/values-bn/strings.xml
index 023be24d29a2..fda27ed1a80a 100644
--- a/packages/SettingsLib/res/values-bn/strings.xml
+++ b/packages/SettingsLib/res/values-bn/strings.xml
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g>-এ ব্যাটারি পুরো চার্জ হয়ে যাবে"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g>-এ ব্যাটারি পুরো চার্জ হয়ে যাবে"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - চার্জিং পজ করা হয়েছে"</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"অজানা"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"চার্জ হচ্ছে"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"দ্রুত চার্জ হচ্ছে"</string>
diff --git a/packages/SettingsLib/res/values-bs/strings.xml b/packages/SettingsLib/res/values-bs/strings.xml
index 3cb419033866..cae96e94518b 100644
--- a/packages/SettingsLib/res/values-bs/strings.xml
+++ b/packages/SettingsLib/res/values-bs/strings.xml
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> do potpune napunjenosti"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> do potpune napunjenosti"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Punjenje je pauzirano"</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Nepoznato"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Punjenje"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Brzo punjenje"</string>
diff --git a/packages/SettingsLib/res/values-ca/strings.xml b/packages/SettingsLib/res/values-ca/strings.xml
index efa29ee1ca38..51a4a024bd27 100644
--- a/packages/SettingsLib/res/values-ca/strings.xml
+++ b/packages/SettingsLib/res/values-ca/strings.xml
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> per completar la càrrega"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g>: <xliff:g id="TIME">%2$s</xliff:g> per completar la càrrega"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g>: la càrrega s\'ha posat en pausa"</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Desconegut"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"S\'està carregant"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Carregant ràpidament"</string>
diff --git a/packages/SettingsLib/res/values-cs/strings.xml b/packages/SettingsLib/res/values-cs/strings.xml
index 3736f93e4a14..a50e52756e02 100644
--- a/packages/SettingsLib/res/values-cs/strings.xml
+++ b/packages/SettingsLib/res/values-cs/strings.xml
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> do úplného nabití"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> do úplného nabití"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> – Nabíjení je pozastaveno"</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Neznámé"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Nabíjí se"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Rychlé nabíjení"</string>
diff --git a/packages/SettingsLib/res/values-da/strings.xml b/packages/SettingsLib/res/values-da/strings.xml
index 849604decc5a..95e01304509d 100644
--- a/packages/SettingsLib/res/values-da/strings.xml
+++ b/packages/SettingsLib/res/values-da/strings.xml
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Fuldt opladet om <xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – fuldt opladet om <xliff:g id="TIME">%2$s</xliff:g>"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Opladningen er sat på pause"</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Ukendt"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Oplader"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Oplader hurtigt"</string>
diff --git a/packages/SettingsLib/res/values-de/strings.xml b/packages/SettingsLib/res/values-de/strings.xml
index d736d5977838..12fce3718d01 100644
--- a/packages/SettingsLib/res/values-de/strings.xml
+++ b/packages/SettingsLib/res/values-de/strings.xml
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Voll in <xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – voll in <xliff:g id="TIME">%2$s</xliff:g>"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> – Ladevorgang angehalten"</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Unbekannt"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Wird aufgeladen"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Schnelles Aufladen"</string>
diff --git a/packages/SettingsLib/res/values-el/strings.xml b/packages/SettingsLib/res/values-el/strings.xml
index e76b933e06ff..f54d5d2334c7 100644
--- a/packages/SettingsLib/res/values-el/strings.xml
+++ b/packages/SettingsLib/res/values-el/strings.xml
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Απομένουν <xliff:g id="TIME">%1$s</xliff:g> για πλήρη φόρτιση"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - Απομένουν <xliff:g id="TIME">%2$s</xliff:g> για πλήρη φόρτιση"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Η φόρτιση τέθηκε σε παύση"</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Άγνωστο"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Φόρτιση"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Ταχεία φόρτιση"</string>
diff --git a/packages/SettingsLib/res/values-en-rAU/strings.xml b/packages/SettingsLib/res/values-en-rAU/strings.xml
index 09bc0656e970..208dbfac0ad1 100644
--- a/packages/SettingsLib/res/values-en-rAU/strings.xml
+++ b/packages/SettingsLib/res/values-en-rAU/strings.xml
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> left until full"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> left until full"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Charging is paused"</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Unknown"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Charging"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Charging rapidly"</string>
diff --git a/packages/SettingsLib/res/values-en-rCA/strings.xml b/packages/SettingsLib/res/values-en-rCA/strings.xml
index 4714a0b44cb9..aac80ab2997b 100644
--- a/packages/SettingsLib/res/values-en-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-en-rCA/strings.xml
@@ -468,6 +468,7 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> left until full"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> left until full"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Charging is paused"</string>
+ <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g> - Charging to <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g>"</string>
<string name="battery_info_status_unknown" msgid="268625384868401114">"Unknown"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Charging"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Charging rapidly"</string>
diff --git a/packages/SettingsLib/res/values-en-rGB/strings.xml b/packages/SettingsLib/res/values-en-rGB/strings.xml
index 09bc0656e970..208dbfac0ad1 100644
--- a/packages/SettingsLib/res/values-en-rGB/strings.xml
+++ b/packages/SettingsLib/res/values-en-rGB/strings.xml
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> left until full"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> left until full"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Charging is paused"</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Unknown"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Charging"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Charging rapidly"</string>
diff --git a/packages/SettingsLib/res/values-en-rIN/strings.xml b/packages/SettingsLib/res/values-en-rIN/strings.xml
index 09bc0656e970..208dbfac0ad1 100644
--- a/packages/SettingsLib/res/values-en-rIN/strings.xml
+++ b/packages/SettingsLib/res/values-en-rIN/strings.xml
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> left until full"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> left until full"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Charging is paused"</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Unknown"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Charging"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Charging rapidly"</string>
diff --git a/packages/SettingsLib/res/values-en-rXC/strings.xml b/packages/SettingsLib/res/values-en-rXC/strings.xml
index f82a7573cec5..bfd7e836a495 100644
--- a/packages/SettingsLib/res/values-en-rXC/strings.xml
+++ b/packages/SettingsLib/res/values-en-rXC/strings.xml
@@ -468,6 +468,7 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‎‎‎‎‎‎‏‏‎‏‎‎‎‎‎‎‏‎‏‎‏‎‏‏‎‏‏‏‎‎‏‎‏‎‎‎‎‏‏‏‏‎‏‏‏‏‏‎‏‎‎‎‎‏‎‎‎‎‏‏‎‎‏‎‎‏‏‎<xliff:g id="TIME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ left until full‎‏‎‎‏‎"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‏‎‏‎‏‎‎‎‎‏‎‎‎‎‎‎‎‎‏‏‎‏‏‏‏‎‏‏‏‎‏‎‏‏‏‎‏‎‎‎‎‎‏‎‏‏‏‏‏‎‎‏‏‏‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎<xliff:g id="LEVEL">%1$s</xliff:g>‎‏‎‎‏‏‏‎ - ‎‏‎‎‏‏‎<xliff:g id="TIME">%2$s</xliff:g>‎‏‎‎‏‏‏‎ left until full‎‏‎‎‏‎"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‎‎‎‎‏‏‎‎‎‎‎‎‎‏‎‏‎‎‏‏‏‎‎‏‏‎‎‎‎‏‏‎‎‏‎‏‎‏‎‏‏‏‎‎‏‎‏‏‏‏‏‎‏‎‏‎‎‏‎‏‎‎‏‎‎‏‏‎<xliff:g id="LEVEL">%1$s</xliff:g>‎‏‎‎‏‏‏‎ - Charging is paused‎‏‎‎‏‎"</string>
+ <string name="power_charging_future_paused" msgid="6829683663982987290">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‏‏‏‏‎‏‏‎‎‎‏‏‏‏‏‏‎‏‎‎‏‎‎‎‏‏‎‏‏‎‏‏‎‏‏‏‏‏‏‏‏‏‏‏‎‏‎‎‎‏‎‎‎‎‎‎‏‏‎‏‎‎‎‏‎‎‏‏‎<xliff:g id="LEVEL">%1$s</xliff:g>‎‏‎‎‏‏‏‎ - Charging to ‎‏‎‎‏‏‎<xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
<string name="battery_info_status_unknown" msgid="268625384868401114">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‎‏‎‏‏‏‎‏‏‏‎‏‎‎‏‎‏‏‎‎‏‎‏‎‏‏‏‎‏‎‏‎‎‎‎‏‎‏‎‏‏‏‎‏‏‏‏‎‎‏‏‏‏‏‏‎‏‏‎‏‎‎Unknown‎‏‎‎‏‎"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‏‎‏‏‎‏‏‎‎‏‎‏‎‏‏‏‎‏‏‏‎‎‏‎‎‎‏‏‏‎‎‎‏‎‏‏‎‏‎‏‎‎‎‎‎‏‏‎‎‏‏‎‏‏‎‎‏‏‎‏‎Charging‎‏‎‎‏‎"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‏‏‏‏‎‏‏‎‎‏‏‏‏‎‎‏‏‏‏‏‎‎‎‏‎‎‎‎‏‏‏‎‏‏‏‏‏‏‎‏‎‎‎‏‎‎‏‎‏‏‎‏‏‎‎‎‎‏‎‏‎Charging rapidly‎‏‎‎‏‎"</string>
diff --git a/packages/SettingsLib/res/values-es-rUS/strings.xml b/packages/SettingsLib/res/values-es-rUS/strings.xml
index 1a4e307c5377..c11d6eb002b7 100644
--- a/packages/SettingsLib/res/values-es-rUS/strings.xml
+++ b/packages/SettingsLib/res/values-es-rUS/strings.xml
@@ -232,7 +232,7 @@
<string name="adb_wireless_list_empty_off" msgid="1713707973837255490">"Para ver y usar los dispositivos disponibles, activa la depuración inalámbrica"</string>
<string name="adb_pair_method_qrcode_title" msgid="6982904096137468634">"Vincular dispositivo mediante código QR"</string>
<string name="adb_pair_method_qrcode_summary" msgid="7130694277228970888">"Vincular dispositivos nuevos mediante escáner de código QR"</string>
- <string name="adb_pair_method_code_title" msgid="1122590300445142904">"Vincular dispositivo con código de sincronización"</string>
+ <string name="adb_pair_method_code_title" msgid="1122590300445142904">"Vincular dispositivo con un código de vinculación"</string>
<string name="adb_pair_method_code_summary" msgid="6370414511333685185">"Vincular dispositivos nuevos mediante código de seis dígitos"</string>
<string name="adb_paired_devices_title" msgid="5268997341526217362">"Dispositivos vinculados"</string>
<string name="adb_wireless_device_connected_summary" msgid="3039660790249148713">"Conectado actualmente"</string>
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> para completar"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> para completar"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Se pausó la carga"</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Desconocido"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Cargando"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Cargando rápidamente"</string>
diff --git a/packages/SettingsLib/res/values-es/strings.xml b/packages/SettingsLib/res/values-es/strings.xml
index fdf99e0acab8..1ef351adb580 100644
--- a/packages/SettingsLib/res/values-es/strings.xml
+++ b/packages/SettingsLib/res/values-es/strings.xml
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> hasta la carga completa"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g>: <xliff:g id="TIME">%2$s</xliff:g> hasta la carga completa"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Carga en pausa"</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Desconocido"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Cargando"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Carga rápida"</string>
diff --git a/packages/SettingsLib/res/values-et/strings.xml b/packages/SettingsLib/res/values-et/strings.xml
index bcd395cc03a0..e156011fd3bc 100644
--- a/packages/SettingsLib/res/values-et/strings.xml
+++ b/packages/SettingsLib/res/values-et/strings.xml
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Täislaadimiseks kulub <xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – täislaadimiseks kulub <xliff:g id="TIME">%2$s</xliff:g>"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> – laadimine on peatatud"</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Tundmatu"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Laadimine"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Kiirlaadimine"</string>
diff --git a/packages/SettingsLib/res/values-eu/strings.xml b/packages/SettingsLib/res/values-eu/strings.xml
index cd1e7d1f7356..e5f399c02302 100644
--- a/packages/SettingsLib/res/values-eu/strings.xml
+++ b/packages/SettingsLib/res/values-eu/strings.xml
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> guztiz kargatu arte"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> guztiz kargatu arte"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Kargatze-prozesua etenda dago"</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Ezezaguna"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Kargatzen"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Bizkor kargatzen"</string>
diff --git a/packages/SettingsLib/res/values-fa/strings.xml b/packages/SettingsLib/res/values-fa/strings.xml
index d40f692380ab..ccacfde64616 100644
--- a/packages/SettingsLib/res/values-fa/strings.xml
+++ b/packages/SettingsLib/res/values-fa/strings.xml
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> تا شارژ کامل باقی مانده است"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> تا شارژ کامل باقی مانده است"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - شارژ موقتاً متوقف شده است"</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"ناشناس"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"در حال شارژ شدن"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"درحال شارژ شدن سریع"</string>
diff --git a/packages/SettingsLib/res/values-fi/strings.xml b/packages/SettingsLib/res/values-fi/strings.xml
index 0d6036abd145..101dc67eb7a3 100644
--- a/packages/SettingsLib/res/values-fi/strings.xml
+++ b/packages/SettingsLib/res/values-fi/strings.xml
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> kunnes täynnä"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> kunnes täynnä"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> – Lataus on keskeytetty"</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Tuntematon"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Ladataan"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Nopea lataus"</string>
diff --git a/packages/SettingsLib/res/values-fr-rCA/strings.xml b/packages/SettingsLib/res/values-fr-rCA/strings.xml
index f2762367af33..ff58a518b833 100644
--- a/packages/SettingsLib/res/values-fr-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-fr-rCA/strings.xml
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> jusqu\'à la recharge complète"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> (<xliff:g id="TIME">%2$s</xliff:g> jusqu\'à la recharge complète)"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - La recharge est interrompue"</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Inconnu"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Charge en cours…"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Recharge rapide"</string>
diff --git a/packages/SettingsLib/res/values-fr/strings.xml b/packages/SettingsLib/res/values-fr/strings.xml
index 7c4afa7b627f..f9725f65f961 100644
--- a/packages/SettingsLib/res/values-fr/strings.xml
+++ b/packages/SettingsLib/res/values-fr/strings.xml
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Chargée à 100 %% dans <xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - chargée à 100 %% dans <xliff:g id="TIME">%2$s</xliff:g>"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> – La recharge est en pause"</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Inconnu"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Batterie en charge"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Charge rapide"</string>
diff --git a/packages/SettingsLib/res/values-gl/strings.xml b/packages/SettingsLib/res/values-gl/strings.xml
index 95d5e215f16b..3ceb99ce9d92 100644
--- a/packages/SettingsLib/res/values-gl/strings.xml
+++ b/packages/SettingsLib/res/values-gl/strings.xml
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> para completar a carga"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> (<xliff:g id="TIME">%2$s</xliff:g> para completar a carga)"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g>: a carga está en pausa"</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Descoñecido"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Cargando"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Cargando rapidamente"</string>
diff --git a/packages/SettingsLib/res/values-gu/strings.xml b/packages/SettingsLib/res/values-gu/strings.xml
index e8acbd5207a1..1821546d4ea9 100644
--- a/packages/SettingsLib/res/values-gu/strings.xml
+++ b/packages/SettingsLib/res/values-gu/strings.xml
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"પૂર્ણ ચાર્જ થવામાં <xliff:g id="TIME">%1$s</xliff:g> બાકી છે"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - પૂર્ણ ચાર્જ થવામાં <xliff:g id="TIME">%2$s</xliff:g> બાકી છે"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - ચાર્જિંગ થોભાવવામાં આવ્યું છે"</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"અજાણ્યું"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"ચાર્જ થઈ રહ્યું છે"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"ઝડપથી ચાર્જ થાય છે"</string>
diff --git a/packages/SettingsLib/res/values-hi/strings.xml b/packages/SettingsLib/res/values-hi/strings.xml
index b1ef50af9535..4b899bb83142 100644
--- a/packages/SettingsLib/res/values-hi/strings.xml
+++ b/packages/SettingsLib/res/values-hi/strings.xml
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> में बैटरी पूरी चार्ज हो जाएगी"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> में बैटरी पूरी चार्ज हो जाएगी"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - चार्जिंग को रोका गया है"</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"अज्ञात"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"चार्ज हो रही है"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"तेज़ चार्ज हो रही है"</string>
diff --git a/packages/SettingsLib/res/values-hr/strings.xml b/packages/SettingsLib/res/values-hr/strings.xml
index 06166f6f52f0..46c8b9030ad3 100644
--- a/packages/SettingsLib/res/values-hr/strings.xml
+++ b/packages/SettingsLib/res/values-hr/strings.xml
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> do napunjenosti"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> do napunjenosti"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> – punjenje je pauzirano"</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Nepoznato"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Punjenje"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Brzo punjenje"</string>
diff --git a/packages/SettingsLib/res/values-hu/strings.xml b/packages/SettingsLib/res/values-hu/strings.xml
index af5a114590b6..d74490f17d92 100644
--- a/packages/SettingsLib/res/values-hu/strings.xml
+++ b/packages/SettingsLib/res/values-hu/strings.xml
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> a teljes töltöttségig"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> a teljes töltöttségig"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> – A töltés szünetel"</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Ismeretlen"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Töltés"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Gyorstöltés"</string>
diff --git a/packages/SettingsLib/res/values-hy/strings.xml b/packages/SettingsLib/res/values-hy/strings.xml
index 61c3e7eddd08..c26f94f0cc59 100644
--- a/packages/SettingsLib/res/values-hy/strings.xml
+++ b/packages/SettingsLib/res/values-hy/strings.xml
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> մինչև լրիվ լիցքավորումը"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> մինչև լրիվ լիցքավորումը"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> – Լիցքավորումը դադարեցված է"</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Անհայտ"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Լիցքավորում"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Արագ լիցքավորում"</string>
diff --git a/packages/SettingsLib/res/values-in/strings.xml b/packages/SettingsLib/res/values-in/strings.xml
index d7f58578d622..203485fc0ee7 100644
--- a/packages/SettingsLib/res/values-in/strings.xml
+++ b/packages/SettingsLib/res/values-in/strings.xml
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> lagi sampai penuh"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> lagi sampai penuh"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Pengisian daya dijeda"</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Tidak diketahui"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Mengisi daya"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Mengisi daya cepat"</string>
diff --git a/packages/SettingsLib/res/values-is/strings.xml b/packages/SettingsLib/res/values-is/strings.xml
index 4862904d6d27..918587730172 100644
--- a/packages/SettingsLib/res/values-is/strings.xml
+++ b/packages/SettingsLib/res/values-is/strings.xml
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> fram að fullri hleðslu"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> fram að fullri hleðslu"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Hlé var gert á hleðslu"</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Óþekkt"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Í hleðslu"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Hröð hleðsla"</string>
diff --git a/packages/SettingsLib/res/values-it/strings.xml b/packages/SettingsLib/res/values-it/strings.xml
index 9e5a2246d0ec..0cd9e1e4b2f9 100644
--- a/packages/SettingsLib/res/values-it/strings.xml
+++ b/packages/SettingsLib/res/values-it/strings.xml
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> alla ricarica completa"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> alla ricarica completa"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Ricarica in pausa"</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Sconosciuta"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"In carica"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Ricarica veloce"</string>
diff --git a/packages/SettingsLib/res/values-iw/strings.xml b/packages/SettingsLib/res/values-iw/strings.xml
index c6afad9c0db3..629310543efb 100644
--- a/packages/SettingsLib/res/values-iw/strings.xml
+++ b/packages/SettingsLib/res/values-iw/strings.xml
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"הזמן הנותר לטעינה מלאה: <xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – הזמן הנותר לטעינה מלאה: <xliff:g id="TIME">%2$s</xliff:g>"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> – הטעינה הושהתה"</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"לא ידוע"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"בטעינה"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"הסוללה נטענת מהר"</string>
diff --git a/packages/SettingsLib/res/values-ja/strings.xml b/packages/SettingsLib/res/values-ja/strings.xml
index 43fe1788dbcd..9a9d6ea2c15a 100644
--- a/packages/SettingsLib/res/values-ja/strings.xml
+++ b/packages/SettingsLib/res/values-ja/strings.xml
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"完了まであと <xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - 完了まであと <xliff:g id="TIME">%2$s</xliff:g>"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - 充電は一時停止中"</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"不明"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"充電中"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"急速充電中"</string>
diff --git a/packages/SettingsLib/res/values-ka/strings.xml b/packages/SettingsLib/res/values-ka/strings.xml
index c498b61a0641..2fa2d8095d36 100644
--- a/packages/SettingsLib/res/values-ka/strings.xml
+++ b/packages/SettingsLib/res/values-ka/strings.xml
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"სრულ დატენვამდე დარჩენილია <xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> — სრულ დატენვამდე დარჩენილია <xliff:g id="TIME">%2$s</xliff:g>"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - დატენვა ᲨეᲩერებულია"</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"უცნობი"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"იტენება"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"სწრაფად იტენება"</string>
diff --git a/packages/SettingsLib/res/values-kk/strings.xml b/packages/SettingsLib/res/values-kk/strings.xml
index 314683bbe8ed..a33200401d68 100644
--- a/packages/SettingsLib/res/values-kk/strings.xml
+++ b/packages/SettingsLib/res/values-kk/strings.xml
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Толық зарядталғанға дейін <xliff:g id="TIME">%1$s</xliff:g> қалды."</string>
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – толық зарядталғанға дейін <xliff:g id="TIME">%2$s</xliff:g> қалды."</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> – зарядтау кідіртілді."</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Белгісіз"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Зарядталуда"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Жылдам зарядталуда"</string>
diff --git a/packages/SettingsLib/res/values-km/strings.xml b/packages/SettingsLib/res/values-km/strings.xml
index 00668fed4820..2d78295ecceb 100644
--- a/packages/SettingsLib/res/values-km/strings.xml
+++ b/packages/SettingsLib/res/values-km/strings.xml
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> ទៀតទើបពេញ"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - នៅសល់ <xliff:g id="TIME">%2$s</xliff:g> ទៀតទើបពេញ"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - ការសាកថ្មត្រូវបានផ្អាក"</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"មិន​ស្គាល់"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"កំពុងសាក​ថ្ម"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"កំពុងសាកថ្មយ៉ាងឆាប់រហ័ស"</string>
diff --git a/packages/SettingsLib/res/values-kn/strings.xml b/packages/SettingsLib/res/values-kn/strings.xml
index ebf742b9a5dd..5db4b1622c34 100644
--- a/packages/SettingsLib/res/values-kn/strings.xml
+++ b/packages/SettingsLib/res/values-kn/strings.xml
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> - ಸಮಯದಲ್ಲಿ ಪೂರ್ತಿ ಚಾರ್ಜ್ ಆಗುತ್ತದೆ"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> ಸಮಯದಲ್ಲಿ ಪೂರ್ತಿ ಚಾರ್ಜ್ ಆಗುತ್ತದೆ"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - ಚಾರ್ಜಿಂಗ್ ಅನ್ನು ವಿರಾಮಗೊಳಿಸಲಾಗಿದೆ"</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"ಅಪರಿಚಿತ"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"ಚಾರ್ಜ್ ಆಗುತ್ತಿದೆ"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"ವೇಗದ ಚಾರ್ಜಿಂಗ್"</string>
diff --git a/packages/SettingsLib/res/values-ko/strings.xml b/packages/SettingsLib/res/values-ko/strings.xml
index 21c809102b3f..30ab26c7450e 100644
--- a/packages/SettingsLib/res/values-ko/strings.xml
+++ b/packages/SettingsLib/res/values-ko/strings.xml
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> 후 충전 완료"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g>: <xliff:g id="TIME">%2$s</xliff:g> 후 충전 완료"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - 충전 일시중지됨"</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"알 수 없음"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"충전 중"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"고속 충전 중"</string>
diff --git a/packages/SettingsLib/res/values-ky/strings.xml b/packages/SettingsLib/res/values-ky/strings.xml
index 076f317aaa4e..baacfd7d27d3 100644
--- a/packages/SettingsLib/res/values-ky/strings.xml
+++ b/packages/SettingsLib/res/values-ky/strings.xml
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> кийин толук кубатталат"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> кийин толук кубатталат"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Кубаттоо тындырылды"</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Белгисиз"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Кубатталууда"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Ыкчам кубатталууда"</string>
diff --git a/packages/SettingsLib/res/values-lo/strings.xml b/packages/SettingsLib/res/values-lo/strings.xml
index 9ebe67a0c8b0..36755f7343b5 100644
--- a/packages/SettingsLib/res/values-lo/strings.xml
+++ b/packages/SettingsLib/res/values-lo/strings.xml
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"ຍັງເຫຼືອອີກ <xliff:g id="TIME">%1$s</xliff:g> ຈຶ່ງຈະສາກເຕັມ"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"ຍັງເຫຼືອອີກ <xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> ຈຶ່ງຈະສາກເຕັມ"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - ການສາກໄຟຖືກຢຸດໄວ້ຊົ່ວຄາວ"</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"ບໍ່ຮູ້ຈັກ"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"ກຳລັງສາກໄຟ"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"ກຳລັງສາກໄຟດ່ວນ"</string>
diff --git a/packages/SettingsLib/res/values-lt/strings.xml b/packages/SettingsLib/res/values-lt/strings.xml
index fcea77873ccd..50652ea56924 100644
--- a/packages/SettingsLib/res/values-lt/strings.xml
+++ b/packages/SettingsLib/res/values-lt/strings.xml
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Liko <xliff:g id="TIME">%1$s</xliff:g>, kol bus visiškai įkrauta"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – liko <xliff:g id="TIME">%2$s</xliff:g>, kol bus visiškai įkrauta"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> – įkrovimas pristabdytas"</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Nežinomas"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Kraunasi..."</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Greitai įkraunama"</string>
diff --git a/packages/SettingsLib/res/values-lv/strings.xml b/packages/SettingsLib/res/values-lv/strings.xml
index c280737424f5..c199feab7974 100644
--- a/packages/SettingsLib/res/values-lv/strings.xml
+++ b/packages/SettingsLib/res/values-lv/strings.xml
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> līdz pilnai uzlādei"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> — <xliff:g id="TIME">%2$s</xliff:g> līdz pilnai uzlādei"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> — uzlāde ir pārtraukta"</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Nezināms"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Uzlāde"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Notiek ātrā uzlāde"</string>
diff --git a/packages/SettingsLib/res/values-mk/strings.xml b/packages/SettingsLib/res/values-mk/strings.xml
index f84990051ab6..957f68bcfbae 100644
--- a/packages/SettingsLib/res/values-mk/strings.xml
+++ b/packages/SettingsLib/res/values-mk/strings.xml
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> до полна батерија"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> до полна батерија"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Полнењето е паузирано"</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Непознато"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Се полни"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Брзо полнење"</string>
diff --git a/packages/SettingsLib/res/values-ml/strings.xml b/packages/SettingsLib/res/values-ml/strings.xml
index cc15e036c8c3..1cc92cad7786 100644
--- a/packages/SettingsLib/res/values-ml/strings.xml
+++ b/packages/SettingsLib/res/values-ml/strings.xml
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"പൂർണ്ണമാകാൻ <xliff:g id="TIME">%1$s</xliff:g> ശേഷിക്കുന്നു"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - പൂർണ്ണമാകാൻ <xliff:g id="TIME">%2$s</xliff:g> ശേഷിക്കുന്നു"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - ചാർജിംഗ് താൽക്കാലികമായി നിർത്തി"</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"അജ്ഞാതം"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"ചാർജ് ചെയ്യുന്നു"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"അതിവേഗ ചാർജിംഗ്"</string>
diff --git a/packages/SettingsLib/res/values-mn/strings.xml b/packages/SettingsLib/res/values-mn/strings.xml
index 3701a30f6050..a107c70caf37 100644
--- a/packages/SettingsLib/res/values-mn/strings.xml
+++ b/packages/SettingsLib/res/values-mn/strings.xml
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Дүүрэх хүртэл <xliff:g id="TIME">%1$s</xliff:g> үлдсэн"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - дүүрэх хүртэл <xliff:g id="TIME">%2$s</xliff:g> үлдсэн"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Цэнэглэхийг түр зогсоосон"</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Тодорхойгүй"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Цэнэглэж байна"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Хурдан цэнэглэж байна"</string>
diff --git a/packages/SettingsLib/res/values-mr/strings.xml b/packages/SettingsLib/res/values-mr/strings.xml
index 3fffc1a14f81..d5c83a8c9a91 100644
--- a/packages/SettingsLib/res/values-mr/strings.xml
+++ b/packages/SettingsLib/res/values-mr/strings.xml
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"पूर्ण चार्ज होण्यासाठी <xliff:g id="TIME">%1$s</xliff:g> शिल्लक आहेत"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - पूर्ण चार्ज होण्यासाठी <xliff:g id="TIME">%2$s</xliff:g> शिल्लक आहे"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - चार्ज करणे थांबवले आहे"</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"अज्ञात"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"चार्ज होत आहे"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"वेगाने चार्ज होत आहे"</string>
diff --git a/packages/SettingsLib/res/values-ms/strings.xml b/packages/SettingsLib/res/values-ms/strings.xml
index 79553c00afea..ae8be8ef0b0f 100644
--- a/packages/SettingsLib/res/values-ms/strings.xml
+++ b/packages/SettingsLib/res/values-ms/strings.xml
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> lagi sebelum penuh"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> lagi sebelum penuh"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Pengecasan dijeda"</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Tidak diketahui"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Mengecas"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Mengecas dgn cepat"</string>
diff --git a/packages/SettingsLib/res/values-my/strings.xml b/packages/SettingsLib/res/values-my/strings.xml
index fc386269ee0f..f16cdcab32f7 100644
--- a/packages/SettingsLib/res/values-my/strings.xml
+++ b/packages/SettingsLib/res/values-my/strings.xml
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"အားပြည့်ရန် <xliff:g id="TIME">%1$s</xliff:g> လိုသည်"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"အားပြည့်ရန် <xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> လိုသည်"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - အားသွင်းခြင်းကို ခဏရပ်ထားသည်"</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"မသိ"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"အားသွင်းနေပါသည်"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"အမြန် အားသွင်းနေသည်"</string>
diff --git a/packages/SettingsLib/res/values-nb/strings.xml b/packages/SettingsLib/res/values-nb/strings.xml
index 59a2517db38c..f7a10a6b26cc 100644
--- a/packages/SettingsLib/res/values-nb/strings.xml
+++ b/packages/SettingsLib/res/values-nb/strings.xml
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Fulladet om <xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – Fulladet om <xliff:g id="TIME">%2$s</xliff:g>"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Ladingen er satt på pause"</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Ukjent"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Lader"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Lader raskt"</string>
diff --git a/packages/SettingsLib/res/values-ne/strings.xml b/packages/SettingsLib/res/values-ne/strings.xml
index 777c897cccab..a966f1560b06 100644
--- a/packages/SettingsLib/res/values-ne/strings.xml
+++ b/packages/SettingsLib/res/values-ne/strings.xml
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"पूरा चार्ज हुन <xliff:g id="TIME">%1$s</xliff:g> लाग्ने छ"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - पूरा चार्ज हुन <xliff:g id="TIME">%2$s</xliff:g> लाग्ने छ"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - चार्ज गर्ने प्रक्रिया रोकिएको छ"</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"अज्ञात"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"चार्ज हुँदै छ"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"द्रुत गतिमा चार्ज गरिँदै छ"</string>
diff --git a/packages/SettingsLib/res/values-nl/strings.xml b/packages/SettingsLib/res/values-nl/strings.xml
index d30a21f00e3d..c5406ee74d7e 100644
--- a/packages/SettingsLib/res/values-nl/strings.xml
+++ b/packages/SettingsLib/res/values-nl/strings.xml
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Vol over <xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - vol over <xliff:g id="TIME">%2$s</xliff:g>"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Opladen is onderbroken"</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Onbekend"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Opladen"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Snel opladen"</string>
diff --git a/packages/SettingsLib/res/values-or/strings.xml b/packages/SettingsLib/res/values-or/strings.xml
index 0120768aedad..37cc25e58bd1 100644
--- a/packages/SettingsLib/res/values-or/strings.xml
+++ b/packages/SettingsLib/res/values-or/strings.xml
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"ପୂର୍ଣ୍ଣ ହେବାକୁ ଆଉ <xliff:g id="TIME">%1$s</xliff:g> ବାକି ଅଛି"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - ପୂର୍ଣ୍ଣ ହେବାକୁ ଆଉ <xliff:g id="TIME">%2$s</xliff:g> ବାକି ଅଛି"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - ଚାର୍ଜିଂକୁ ବିରତ କରାଯାଇଛି"</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"ଅଜ୍ଞାତ"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"ଚାର୍ଜ ହେଉଛି"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"ଶୀଘ୍ର ଚାର୍ଜ ହେଉଛି"</string>
diff --git a/packages/SettingsLib/res/values-pa/strings.xml b/packages/SettingsLib/res/values-pa/strings.xml
index 14817679639f..767cbc5f0022 100644
--- a/packages/SettingsLib/res/values-pa/strings.xml
+++ b/packages/SettingsLib/res/values-pa/strings.xml
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"ਬੈਟਰੀ ਪੂਰੀ ਚਾਰਜ ਹੋਣ ਵਿੱਚ <xliff:g id="TIME">%1$s</xliff:g> ਬਾਕੀ"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - ਬੈਟਰੀ ਪੂਰੀ ਚਾਰਜ ਹੋਣ ਵਿੱਚ <xliff:g id="TIME">%2$s</xliff:g> ਬਾਕੀ"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - ਚਾਰਜਿੰਗ ਨੂੰ ਰੋਕਿਆ ਗਿਆ ਹੈ"</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"ਅਗਿਆਤ"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"ਚਾਰਜ ਹੋ ਰਹੀ ਹੈ"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"ਤੇਜ਼ ਚਾਰਜ ਹੋ ਰਹੀ ਹੈ"</string>
diff --git a/packages/SettingsLib/res/values-pl/strings.xml b/packages/SettingsLib/res/values-pl/strings.xml
index 83f8e49e5d44..48bc82e8ee4c 100644
--- a/packages/SettingsLib/res/values-pl/strings.xml
+++ b/packages/SettingsLib/res/values-pl/strings.xml
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> do pełnego naładowania"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> do pełnego naładowania"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> – ładowanie zostało wstrzymane"</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Nieznane"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Ładowanie"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Szybkie ładowanie"</string>
diff --git a/packages/SettingsLib/res/values-pt-rBR/strings.xml b/packages/SettingsLib/res/values-pt-rBR/strings.xml
index d9a7c6941a44..68a1c49c486c 100644
--- a/packages/SettingsLib/res/values-pt-rBR/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rBR/strings.xml
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> até a conclusão"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g>: <xliff:g id="TIME">%2$s</xliff:g> até a conclusão"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g>: o carregamento está pausado"</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Desconhecido"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Carregando"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Carregando rápido"</string>
diff --git a/packages/SettingsLib/res/values-pt-rPT/strings.xml b/packages/SettingsLib/res/values-pt-rPT/strings.xml
index c24589c249b1..87bab5072057 100644
--- a/packages/SettingsLib/res/values-pt-rPT/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rPT/strings.xml
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> até à carga máxima"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> até à carga máxima"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> – O carregamento está pausado"</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Desconhecido"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"A carregar"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Carregamento rápido"</string>
diff --git a/packages/SettingsLib/res/values-pt/strings.xml b/packages/SettingsLib/res/values-pt/strings.xml
index d9a7c6941a44..68a1c49c486c 100644
--- a/packages/SettingsLib/res/values-pt/strings.xml
+++ b/packages/SettingsLib/res/values-pt/strings.xml
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> até a conclusão"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g>: <xliff:g id="TIME">%2$s</xliff:g> até a conclusão"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g>: o carregamento está pausado"</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Desconhecido"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Carregando"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Carregando rápido"</string>
diff --git a/packages/SettingsLib/res/values-ro/strings.xml b/packages/SettingsLib/res/values-ro/strings.xml
index 04c58aecb2b0..f81ef4c89e2d 100644
--- a/packages/SettingsLib/res/values-ro/strings.xml
+++ b/packages/SettingsLib/res/values-ro/strings.xml
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> până la finalizare"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> până la finalizare"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> – Încărcarea este întreruptă"</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Necunoscut"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Se încarcă"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Se încarcă rapid"</string>
diff --git a/packages/SettingsLib/res/values-ru/strings.xml b/packages/SettingsLib/res/values-ru/strings.xml
index 704d84861a7a..4dcee136b870 100644
--- a/packages/SettingsLib/res/values-ru/strings.xml
+++ b/packages/SettingsLib/res/values-ru/strings.xml
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> до полной зарядки"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> до полной зарядки"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g>: зарядка приостановлена"</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Неизвестно"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Идет зарядка"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Быстрая зарядка"</string>
diff --git a/packages/SettingsLib/res/values-si/strings.xml b/packages/SettingsLib/res/values-si/strings.xml
index d67060ebb14e..17c59eaad41d 100644
--- a/packages/SettingsLib/res/values-si/strings.xml
+++ b/packages/SettingsLib/res/values-si/strings.xml
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"සම්පූර්ණ වීමට <xliff:g id="TIME">%1$s</xliff:g>ක් ඉතිරියි"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - සම්පූර්ණ වීමට <xliff:g id="TIME">%2$s</xliff:g>ක් ඉතිරියි"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - ආරෝපණය විරාම කර ඇත"</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"නොදනී"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"ආරෝපණය වෙමින්"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"ශීඝ්‍ර ආරෝපණය"</string>
diff --git a/packages/SettingsLib/res/values-sk/strings.xml b/packages/SettingsLib/res/values-sk/strings.xml
index 198289329941..d9ce6cf77e68 100644
--- a/packages/SettingsLib/res/values-sk/strings.xml
+++ b/packages/SettingsLib/res/values-sk/strings.xml
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> do úplného nabitia"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> do úplného nabitia"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Nabíjanie je pozastavené"</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Neznáme"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Nabíja sa"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Rýchle nabíjanie"</string>
diff --git a/packages/SettingsLib/res/values-sl/strings.xml b/packages/SettingsLib/res/values-sl/strings.xml
index 262ecfdca78d..8f9b059c3b9a 100644
--- a/packages/SettingsLib/res/values-sl/strings.xml
+++ b/packages/SettingsLib/res/values-sl/strings.xml
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Še <xliff:g id="TIME">%1$s</xliff:g> do napolnjenosti"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – še <xliff:g id="TIME">%2$s</xliff:g> do napolnjenosti"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> – Polnjenje je začasno zaustavljeno"</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Neznano"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Polnjenje"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Hitro polnjenje"</string>
diff --git a/packages/SettingsLib/res/values-sq/strings.xml b/packages/SettingsLib/res/values-sq/strings.xml
index d0f1f7c0133b..d467eeab1912 100644
--- a/packages/SettingsLib/res/values-sq/strings.xml
+++ b/packages/SettingsLib/res/values-sq/strings.xml
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> derisa të mbushet"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> derisa të mbushet"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Karikimi është vendosur në pauzë"</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"I panjohur"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Po karikohet"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Karikim i shpejtë"</string>
diff --git a/packages/SettingsLib/res/values-sr/strings.xml b/packages/SettingsLib/res/values-sr/strings.xml
index b102d3ad5ecc..9cc43d98c486 100644
--- a/packages/SettingsLib/res/values-sr/strings.xml
+++ b/packages/SettingsLib/res/values-sr/strings.xml
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> до краја пуњења"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> до краја пуњења"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> – Пуњење је заустављено"</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Непознато"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Пуни се"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Брзо се пуни"</string>
diff --git a/packages/SettingsLib/res/values-sv/strings.xml b/packages/SettingsLib/res/values-sv/strings.xml
index 17a06c21d4fa..89deafa2cd67 100644
--- a/packages/SettingsLib/res/values-sv/strings.xml
+++ b/packages/SettingsLib/res/values-sv/strings.xml
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> kvar tills fulladdat"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> kvar tills fulladdat"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Laddningen har pausats"</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Okänd"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Laddar"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Laddas snabbt"</string>
diff --git a/packages/SettingsLib/res/values-sw/strings.xml b/packages/SettingsLib/res/values-sw/strings.xml
index 72a3751cee08..1fa5eddd6e86 100644
--- a/packages/SettingsLib/res/values-sw/strings.xml
+++ b/packages/SettingsLib/res/values-sw/strings.xml
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Zimesalia <xliff:g id="TIME">%1$s</xliff:g> ijae chaji"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> zimesalia ijae chaji"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Imesitisha kuchaji"</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Haijulikani"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Inachaji"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Inachaji kwa kasi"</string>
diff --git a/packages/SettingsLib/res/values-ta/strings.xml b/packages/SettingsLib/res/values-ta/strings.xml
index 8714958b1993..1b7b643ab081 100644
--- a/packages/SettingsLib/res/values-ta/strings.xml
+++ b/packages/SettingsLib/res/values-ta/strings.xml
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"முழுவதும் சார்ஜாக <xliff:g id="TIME">%1$s</xliff:g> ஆகும்"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - முழுவதும் சார்ஜாக <xliff:g id="TIME">%2$s</xliff:g> ஆகும்"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - சார்ஜ் ஏறுவது இடைநிறுத்தப்பட்டுள்ளது"</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"அறியப்படாத"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"சார்ஜ் ஆகிறது"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"வேகமாக சார்ஜாகிறது"</string>
diff --git a/packages/SettingsLib/res/values-te/strings.xml b/packages/SettingsLib/res/values-te/strings.xml
index 5e00b1d51848..27e9dbd09978 100644
--- a/packages/SettingsLib/res/values-te/strings.xml
+++ b/packages/SettingsLib/res/values-te/strings.xml
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g>లో పూర్తిగా ఛార్జ్ అవుతుంది"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g>లో పూర్తిగా ఛార్జ్ అవుతుంది"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - ఛార్జింగ్ పాజ్ చేయబడింది"</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"తెలియదు"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"ఛార్జ్ అవుతోంది"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"వేగవంతమైన ఛార్జింగ్"</string>
diff --git a/packages/SettingsLib/res/values-th/strings.xml b/packages/SettingsLib/res/values-th/strings.xml
index f386e5da132a..6b9c077a4f1b 100644
--- a/packages/SettingsLib/res/values-th/strings.xml
+++ b/packages/SettingsLib/res/values-th/strings.xml
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"อีก <xliff:g id="TIME">%1$s</xliff:g>จึงจะเต็ม"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - อีก <xliff:g id="TIME">%2$s</xliff:g> จึงจะเต็ม"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - การชาร์จหยุดชั่วคราว"</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"ไม่ทราบ"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"กำลังชาร์จ"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"กำลังชาร์จอย่างเร็ว"</string>
diff --git a/packages/SettingsLib/res/values-tl/strings.xml b/packages/SettingsLib/res/values-tl/strings.xml
index db2738276bb9..fddc1d0cf8fb 100644
--- a/packages/SettingsLib/res/values-tl/strings.xml
+++ b/packages/SettingsLib/res/values-tl/strings.xml
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> na lang bago mapuno"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> na lang bago mapuno"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Naka-pause ang pag-charge"</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Hindi Kilala"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Nagcha-charge"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Mabilis na charge"</string>
diff --git a/packages/SettingsLib/res/values-tr/strings.xml b/packages/SettingsLib/res/values-tr/strings.xml
index 8f4bae49cea5..31dcedb8eeaf 100644
--- a/packages/SettingsLib/res/values-tr/strings.xml
+++ b/packages/SettingsLib/res/values-tr/strings.xml
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Tamamen şarj olmasına <xliff:g id="TIME">%1$s</xliff:g> kaldı"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - Tamamen şarj olmasına <xliff:g id="TIME">%2$s</xliff:g> kaldı"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g>: Şarj işlemi duraklatıldı"</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Bilinmiyor"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Şarj oluyor"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Hızlı şarj oluyor"</string>
diff --git a/packages/SettingsLib/res/values-uk/strings.xml b/packages/SettingsLib/res/values-uk/strings.xml
index a0c68c140f91..2fdc2273d2ad 100644
--- a/packages/SettingsLib/res/values-uk/strings.xml
+++ b/packages/SettingsLib/res/values-uk/strings.xml
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> до повного заряду"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> до повного заряду"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Заряджання призупинено"</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Невідомо"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Заряджається"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Швидке заряджання"</string>
diff --git a/packages/SettingsLib/res/values-ur/strings.xml b/packages/SettingsLib/res/values-ur/strings.xml
index d0a120429bb6..82dbdb3c1597 100644
--- a/packages/SettingsLib/res/values-ur/strings.xml
+++ b/packages/SettingsLib/res/values-ur/strings.xml
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"مکمل چارج ہونے میں <xliff:g id="TIME">%1$s</xliff:g> باقی ہے"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"مکمل چارج ہونے میں <xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> باقی ہے"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - چارجنگ موقوف ہے"</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"نامعلوم"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"چارج ہو رہا ہے"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"تیزی سے چارج ہو رہا ہے"</string>
diff --git a/packages/SettingsLib/res/values-uz/strings.xml b/packages/SettingsLib/res/values-uz/strings.xml
index efb5eb974afe..ab997b6c649c 100644
--- a/packages/SettingsLib/res/values-uz/strings.xml
+++ b/packages/SettingsLib/res/values-uz/strings.xml
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Toʻlishiga <xliff:g id="TIME">%1$s</xliff:g> qoldi"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – Toʻlishiga <xliff:g id="TIME">%2$s</xliff:g> qoldi"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Quvvatlash pauza qilindi"</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Noma’lum"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Quvvat olmoqda"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Tezkor quvvat olmoqda"</string>
diff --git a/packages/SettingsLib/res/values-vi/strings.xml b/packages/SettingsLib/res/values-vi/strings.xml
index f7f4c0e3abb3..44c820a5a548 100644
--- a/packages/SettingsLib/res/values-vi/strings.xml
+++ b/packages/SettingsLib/res/values-vi/strings.xml
@@ -268,7 +268,7 @@
<string name="mock_location_app_set" msgid="4706722469342913843">"Ứng dụng vị trí mô phỏng: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="debug_networking_category" msgid="6829757985772659599">"Mạng"</string>
<string name="wifi_display_certification" msgid="1805579519992520381">"Chứng nhận hiển thị không dây"</string>
- <string name="wifi_verbose_logging" msgid="1785910450009679371">"Bật ghi nhật ký chi tiết Wi‑Fi"</string>
+ <string name="wifi_verbose_logging" msgid="1785910450009679371">"Bật tính năng ghi nhật ký chi tiết Wi‑Fi"</string>
<string name="wifi_scan_throttling" msgid="2985624788509913617">"Hạn chế quét tìm Wi-Fi"</string>
<string name="wifi_non_persistent_mac_randomization" msgid="7482769677894247316">"Tạo địa chỉ MAC ngẫu nhiên, không cố định mỗi khi kết nối Wi-Fi"</string>
<string name="mobile_data_always_on" msgid="8275958101875563572">"Dữ liệu di động luôn hoạt động"</string>
@@ -280,7 +280,7 @@
<string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Chọn phiên bản Bluetooth AVRCP"</string>
<string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Phiên bản Bluetooth MAP"</string>
<string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"Chọn phiên bản Bluetooth MAP"</string>
- <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"Codec âm thanh Bluetooth"</string>
+ <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"Bộ mã hoá và giải mã âm thanh qua Bluetooth"</string>
<string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"Kích hoạt chế độ chọn codec\nâm thanh Bluetooth"</string>
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"Tốc độ lấy mẫu âm thanh Bluetooth"</string>
<string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="5876305103137067798">"Kích hoạt chế độ chọn codec\nâm thanh Bluetooth: Tần số lấy mẫu"</string>
@@ -374,7 +374,7 @@
<string name="window_blurs" msgid="6831008984828425106">"Cho phép làm mờ cửa sổ"</string>
<string name="force_msaa" msgid="4081288296137775550">"Bắt buộc 4x MSAA"</string>
<string name="force_msaa_summary" msgid="9070437493586769500">"Bật 4x MSAA trong ứng dụng OpenGL ES 2.0"</string>
- <string name="show_non_rect_clip" msgid="7499758654867881817">"Gỡ lỗi hoạt động của clip không phải là hình chữ nhật"</string>
+ <string name="show_non_rect_clip" msgid="7499758654867881817">"Gỡ lỗi hoạt động của đoạn không phải hình chữ nhật"</string>
<string name="track_frame_time" msgid="522674651937771106">"Kết xuất HWUI cấu hình"</string>
<string name="enable_gpu_debug_layers" msgid="4986675516188740397">"Bật lớp gỡ lỗi GPU"</string>
<string name="enable_gpu_debug_layers_summary" msgid="4921521407377170481">"Cho phép tải lớp gỡ lỗi GPU cho ứng dụng gỡ lỗi"</string>
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> nữa là pin đầy"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> nữa là pin đầy"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> – Đã tạm dừng sạc"</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Không xác định"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Đang sạc"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Đang sạc nhanh"</string>
diff --git a/packages/SettingsLib/res/values-zh-rCN/strings.xml b/packages/SettingsLib/res/values-zh-rCN/strings.xml
index f536bb9e3511..eae1c391415d 100644
--- a/packages/SettingsLib/res/values-zh-rCN/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rCN/strings.xml
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"还需<xliff:g id="TIME">%1$s</xliff:g>充满"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - 还需<xliff:g id="TIME">%2$s</xliff:g>充满"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - 充电已暂停"</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"未知"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"正在充电"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"正在快速充电"</string>
diff --git a/packages/SettingsLib/res/values-zh-rHK/strings.xml b/packages/SettingsLib/res/values-zh-rHK/strings.xml
index 357302b96c90..4371bb30e865 100644
--- a/packages/SettingsLib/res/values-zh-rHK/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rHK/strings.xml
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g>後充滿電"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g>後充滿電"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - 已暫停充電"</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"未知"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"充電中"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"快速充電中"</string>
diff --git a/packages/SettingsLib/res/values-zh-rTW/strings.xml b/packages/SettingsLib/res/values-zh-rTW/strings.xml
index 4ae28304a17c..d76a4a45288f 100644
--- a/packages/SettingsLib/res/values-zh-rTW/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rTW/strings.xml
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g>後充飽"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g>後充飽"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - 已暫停充電"</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"不明"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"充電中"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"快速充電中"</string>
diff --git a/packages/SettingsLib/res/values-zu/strings.xml b/packages/SettingsLib/res/values-zu/strings.xml
index c113dc8a799f..4dc7651ffc75 100644
--- a/packages/SettingsLib/res/values-zu/strings.xml
+++ b/packages/SettingsLib/res/values-zu/strings.xml
@@ -468,6 +468,8 @@
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> okusele kuze kugcwale"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> okusele kuze kugcwale"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Ukushaja kumisiwe okwesikhashana"</string>
+ <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Akwaziwa"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Iyashaja"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Ishaja ngokushesha"</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index 61c7fb916404..2951001f5368 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -163,6 +163,11 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
mUnpairing = false;
}
+ /** Clears any pending messages in the message queue. */
+ public void release() {
+ mHandler.removeCallbacksAndMessages(null);
+ }
+
private void initDrawableCache() {
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
int cacheSize = maxMemory / 8;
@@ -1441,11 +1446,13 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
final boolean tmpJustDiscovered = mJustDiscovered;
final HearingAidInfo tmpHearingAidInfo = mHearingAidInfo;
// Set main device from sub device
+ release();
mDevice = mSubDevice.mDevice;
mRssi = mSubDevice.mRssi;
mJustDiscovered = mSubDevice.mJustDiscovered;
mHearingAidInfo = mSubDevice.mHearingAidInfo;
// Set sub device from backup
+ mSubDevice.release();
mSubDevice.mDevice = tmpDevice;
mSubDevice.mRssi = tmpRssi;
mSubDevice.mJustDiscovered = tmpJustDiscovered;
@@ -1471,6 +1478,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
* Remove a device from the member device sets.
*/
public void removeMemberDevice(CachedBluetoothDevice memberDevice) {
+ memberDevice.release();
mMemberDevices.remove(memberDevice);
}
@@ -1488,11 +1496,13 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
final short tmpRssi = mRssi;
final boolean tmpJustDiscovered = mJustDiscovered;
// Set main device from sub device
+ release();
mDevice = newMainDevice.mDevice;
mRssi = newMainDevice.mRssi;
mJustDiscovered = newMainDevice.mJustDiscovered;
// Set sub device from backup
+ newMainDevice.release();
newMainDevice.mDevice = tmpDevice;
newMainDevice.mRssi = tmpRssi;
newMainDevice.mJustDiscovered = tmpJustDiscovered;
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
index dd56bdefb77f..221836b02347 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
@@ -223,8 +223,14 @@ public class CachedBluetoothDeviceManager {
public synchronized void clearNonBondedDevices() {
clearNonBondedSubDevices();
- mCachedDevices.removeIf(cachedDevice
- -> cachedDevice.getBondState() == BluetoothDevice.BOND_NONE);
+ final List<CachedBluetoothDevice> removedCachedDevice = new ArrayList<>();
+ mCachedDevices.stream()
+ .filter(cachedDevice -> cachedDevice.getBondState() == BluetoothDevice.BOND_NONE)
+ .forEach(cachedDevice -> {
+ cachedDevice.release();
+ removedCachedDevice.add(cachedDevice);
+ });
+ mCachedDevices.removeAll(removedCachedDevice);
}
private void clearNonBondedSubDevices() {
@@ -245,6 +251,7 @@ public class CachedBluetoothDeviceManager {
if (subDevice != null
&& subDevice.getDevice().getBondState() == BluetoothDevice.BOND_NONE) {
// Sub device exists and it is not bonded
+ subDevice.release();
cachedDevice.setSubDevice(null);
}
}
@@ -294,6 +301,7 @@ public class CachedBluetoothDeviceManager {
}
if (cachedDevice.getBondState() != BluetoothDevice.BOND_BONDED) {
cachedDevice.setJustDiscovered(false);
+ cachedDevice.release();
mCachedDevices.remove(i);
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java
index f06aab396ec0..6b7f733b5413 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java
@@ -176,11 +176,11 @@ public class HapClientProfile implements LocalBluetoothProfile {
* @param device is the device for which we want to know if supports synchronized presets
* @return {@code true} if the device supports synchronized presets
*/
- public boolean supportSynchronizedPresets(@NonNull BluetoothDevice device) {
+ public boolean supportsSynchronizedPresets(@NonNull BluetoothDevice device) {
if (mService == null) {
return false;
}
- return mService.supportSynchronizedPresets(device);
+ return mService.supportsSynchronizedPresets(device);
}
/**
@@ -189,11 +189,11 @@ public class HapClientProfile implements LocalBluetoothProfile {
* @param device is the device for which we want to know if supports independent presets
* @return {@code true} if the device supports independent presets
*/
- public boolean supportIndependentPresets(@NonNull BluetoothDevice device) {
+ public boolean supportsIndependentPresets(@NonNull BluetoothDevice device) {
if (mService == null) {
return false;
}
- return mService.supportIndependentPresets(device);
+ return mService.supportsIndependentPresets(device);
}
/**
@@ -202,11 +202,11 @@ public class HapClientProfile implements LocalBluetoothProfile {
* @param device is the device for which we want to know if supports dynamic presets
* @return {@code true} if the device supports dynamic presets
*/
- public boolean supportDynamicPresets(@NonNull BluetoothDevice device) {
+ public boolean supportsDynamicPresets(@NonNull BluetoothDevice device) {
if (mService == null) {
return false;
}
- return mService.supportDynamicPresets(device);
+ return mService.supportsDynamicPresets(device);
}
/**
@@ -215,11 +215,11 @@ public class HapClientProfile implements LocalBluetoothProfile {
* @param device is the device for which we want to know if supports writable presets
* @return {@code true} if the device supports writable presets
*/
- public boolean supportWritablePresets(@NonNull BluetoothDevice device) {
+ public boolean supportsWritablePresets(@NonNull BluetoothDevice device) {
if (mService == null) {
return false;
}
- return mService.supportWritablePresets(device);
+ return mService.supportsWritablePresets(device);
}
@Override
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
index 65671a26690e..77c19a1d903e 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
@@ -998,10 +998,12 @@ public class CachedBluetoothDeviceTest {
mCachedDevice.switchSubDeviceContent();
+ verify(mCachedDevice).release();
assertThat(mCachedDevice.mRssi).isEqualTo(RSSI_2);
assertThat(mCachedDevice.mJustDiscovered).isEqualTo(JUSTDISCOVERED_2);
assertThat(mCachedDevice.mDevice).isEqualTo(mSubDevice);
assertThat(mCachedDevice.getDeviceSide()).isEqualTo(HearingAidInfo.DeviceSide.SIDE_RIGHT);
+ verify(mSubCachedDevice).release();
assertThat(mSubCachedDevice.mRssi).isEqualTo(RSSI_1);
assertThat(mSubCachedDevice.mJustDiscovered).isEqualTo(JUSTDISCOVERED_1);
assertThat(mSubCachedDevice.mDevice).isEqualTo(mDevice);
diff --git a/packages/SettingsProvider/res/xml/bookmarks.xml b/packages/SettingsProvider/res/xml/bookmarks.xml
index 454f4562a239..4b97b4779e1b 100644
--- a/packages/SettingsProvider/res/xml/bookmarks.xml
+++ b/packages/SettingsProvider/res/xml/bookmarks.xml
@@ -19,7 +19,6 @@
Bookmarks for vendor apps should be added to a bookmarks resource overlay; not here.
Typical shortcuts (not necessarily defined here):
- 'a': Calculator
'b': Browser
'c': Contacts
'e': Email
@@ -29,13 +28,11 @@
'p': Music
's': SMS
't': Talk
+ 'u': Calculator
'y': YouTube
-->
<bookmarks>
<bookmark
- category="android.intent.category.APP_CALCULATOR"
- shortcut="a" />
- <bookmark
category="android.intent.category.APP_BROWSER"
shortcut="b" />
<bookmark
@@ -46,7 +43,7 @@
shortcut="e" />
<bookmark
category="android.intent.category.APP_CALENDAR"
- shortcut="l" />
+ shortcut="k" />
<bookmark
category="android.intent.category.APP_MAPS"
shortcut="m" />
@@ -56,4 +53,7 @@
<bookmark
category="android.intent.category.APP_MESSAGING"
shortcut="s" />
+ <bookmark
+ category="android.intent.category.APP_CALCULATOR"
+ shortcut="u" />
</bookmarks>
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index cf7c7c5751dd..46876b457b54 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -336,6 +336,7 @@ public class GlobalSettingsValidators {
VALIDATORS.put(Global.Wearable.COOLDOWN_MODE_ON, BOOLEAN_VALIDATOR);
VALIDATORS.put(
Global.Wearable.GESTURE_TOUCH_AND_HOLD_WATCH_FACE_ENABLED, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Global.Wearable.RSB_WAKE_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.Wearable.SCREEN_UNLOCK_SOUND_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.Wearable.CHARGING_SOUNDS_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.Wearable.BEDTIME_MODE, BOOLEAN_VALIDATOR);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 0b7b2f935e91..9192086de98a 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -1144,7 +1144,7 @@ public class SettingsProvider extends ContentProvider {
Slog.v(LOG_TAG, "getConfigSetting(" + name + ")");
}
- DeviceConfig.enforceReadPermission(/*namespace=*/name.split("/")[0]);
+ Settings.Config.enforceReadPermission(/*namespace=*/name.split("/")[0]);
// Get the value.
synchronized (mLock) {
@@ -1317,7 +1317,7 @@ public class SettingsProvider extends ContentProvider {
Slog.v(LOG_TAG, "getAllConfigFlags() for " + prefix);
}
- DeviceConfig.enforceReadPermission(
+ Settings.Config.enforceReadPermission(
prefix != null ? prefix.split("/")[0] : null);
synchronized (mLock) {
@@ -3595,8 +3595,8 @@ public class SettingsProvider extends ContentProvider {
private Uri getNotificationUriFor(int key, String name) {
if (isConfigSettingsKey(key)) {
- return (name != null) ? Uri.withAppendedPath(DeviceConfig.CONTENT_URI, name)
- : DeviceConfig.CONTENT_URI;
+ return (name != null) ? Uri.withAppendedPath(Settings.Config.CONTENT_URI, name)
+ : Settings.Config.CONTENT_URI;
} else if (isGlobalSettingsKey(key)) {
return (name != null) ? Uri.withAppendedPath(Settings.Global.CONTENT_URI, name)
: Settings.Global.CONTENT_URI;
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 24c14352cb61..153f0b4711c5 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -659,7 +659,8 @@ public class SettingsBackupTest {
Settings.Global.Wearable.SCREEN_UNLOCK_SOUND_ENABLED,
Settings.Global.Wearable.BEDTIME_MODE,
Settings.Global.Wearable.BEDTIME_HARD_MODE,
- Settings.Global.Wearable.EARLY_UPDATES_STATUS);
+ Settings.Global.Wearable.EARLY_UPDATES_STATUS,
+ Settings.Global.Wearable.RSB_WAKE_ENABLED);
private static final Set<String> BACKUP_DENY_LIST_SECURE_SETTINGS =
newHashSet(
@@ -771,6 +772,7 @@ public class SettingsBackupTest {
Settings.Secure.SLEEP_TIMEOUT,
Settings.Secure.SMS_DEFAULT_APPLICATION,
Settings.Secure.SPELL_CHECKER_ENABLED, // Intentionally removed in Q
+ Settings.Secure.STYLUS_BUTTONS_DISABLED,
Settings.Secure.TRUST_AGENTS_INITIALIZED,
Settings.Secure.KNOWN_TRUST_AGENTS_INITIALIZED,
Settings.Secure.TV_APP_USES_NON_SYSTEM_INPUTS,
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/DeviceConfigServiceTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/DeviceConfigServiceTest.java
index e588b3d594ca..753378bc487b 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/DeviceConfigServiceTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/DeviceConfigServiceTest.java
@@ -22,7 +22,6 @@ import static junit.framework.Assert.assertNull;
import android.content.ContentResolver;
import android.os.Bundle;
-import android.provider.DeviceConfig;
import android.provider.Settings;
import androidx.test.InstrumentationRegistry;
@@ -180,14 +179,14 @@ public class DeviceConfigServiceTest {
args.putBoolean(Settings.CALL_METHOD_MAKE_DEFAULT_KEY, true);
}
resolver.call(
- DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, compositeName, args);
+ Settings.Config.CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, compositeName, args);
}
private static String getFromContentProvider(ContentResolver resolver, String namespace,
String key) {
String compositeName = namespace + "/" + key;
Bundle result = resolver.call(
- DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_GET_CONFIG, compositeName, null);
+ Settings.Config.CONTENT_URI, Settings.CALL_METHOD_GET_CONFIG, compositeName, null);
assertNotNull(result);
return result.getString(Settings.NameValueTable.VALUE);
}
@@ -196,7 +195,8 @@ public class DeviceConfigServiceTest {
String key) {
String compositeName = namespace + "/" + key;
Bundle result = resolver.call(
- DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, compositeName, null);
+ Settings.Config.CONTENT_URI,
+ Settings.CALL_METHOD_DELETE_CONFIG, compositeName, null);
assertNotNull(result);
return compositeName.equals(result.getString(Settings.NameValueTable.VALUE));
}
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 680a0a17dca8..68455c8347ff 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -360,6 +360,9 @@
<!-- Permission needed to test wallpaper dimming -->
<uses-permission android:name="android.permission.SET_WALLPAPER_DIM_AMOUNT" />
+ <!-- Permission needed to test wallpapers supporting ambient mode -->
+ <uses-permission android:name="android.permission.AMBIENT_WALLPAPER" />
+
<!-- Permission required to test ContentResolver caching. -->
<uses-permission android:name="android.permission.CACHE_CONTENT" />
@@ -754,6 +757,9 @@
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SYSTEM_EXEMPTED" />
<!-- Permission required for CTS test - CtsAppFgsTestCases -->
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_FILE_MANAGEMENT" />
+
+ <!-- Permission required for CTS test - CtsAppFgsTestCases -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
<!-- Permissions required for CTS test - CtsAppFgsTestCases -->
@@ -771,6 +777,9 @@
<!-- Permissions required for CTS test - CtsAppFgsTestCases -->
<uses-permission android:name="android.permission.USE_EXACT_ALARM" />
+ <!-- Permission required for CTS test - CtsHardwareTestCases -->
+ <uses-permission android:name="android.permission.REMAP_MODIFIER_KEYS" />
+
<!-- Permissions required for CTS test - CtsAppFgsTestCases -->
<uses-permission android:name="android.permission.health.READ_ACTIVE_CALORIES_BURNED" />
<uses-permission android:name="android.permission.health.READ_BASAL_BODY_TEMPERATURE" />
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 7db68b085090..a8216e89b0fd 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -914,6 +914,29 @@
<service android:name=".controls.controller.AuxiliaryPersistenceWrapper$DeletionJobService"
android:permission="android.permission.BIND_JOB_SERVICE"/>
+ <!-- region Note Task -->
+ <activity
+ android:name=".notetask.shortcut.CreateNoteTaskShortcutActivity"
+ android:enabled="false"
+ android:exported="true"
+ android:excludeFromRecents="true"
+ android:theme="@android:style/Theme.NoDisplay"
+ android:label="@string/note_task_button_label"
+ android:icon="@drawable/ic_note_task_button">
+
+ <intent-filter>
+ <action android:name="android.intent.action.CREATE_SHORTCUT" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+
+ <activity
+ android:name=".notetask.shortcut.LaunchNoteTaskActivity"
+ android:exported="true"
+ android:excludeFromRecents="true"
+ android:theme="@android:style/Theme.NoDisplay" />
+ <!-- endregion -->
+
<!-- started from ControlsRequestReceiver -->
<activity
android:name=".controls.management.ControlsRequestDialog"
diff --git a/packages/SystemUI/animation/Android.bp b/packages/SystemUI/animation/Android.bp
index e6ac48ff5af8..8acc2f8adcf9 100644
--- a/packages/SystemUI/animation/Android.bp
+++ b/packages/SystemUI/animation/Android.bp
@@ -34,28 +34,13 @@ android_library {
"res",
],
- static_libs: ["androidx.core_core-animation-nodeps"],
-
- manifest: "AndroidManifest.xml",
- kotlincflags: ["-Xjvm-default=all"],
-}
-
-android_test {
- name: "SystemUIAnimationLibTests",
-
static_libs: [
- "SystemUIAnimationLib",
- "androidx.test.ext.junit",
- "androidx.test.rules",
- "testables",
- ],
- libs: [
- "android.test.base",
- ],
- srcs: [
- "**/*.java",
- "**/*.kt",
+ "PluginCoreLib",
+ "androidx.core_core-animation-nodeps",
+ "androidx.core_core-ktx",
+ "androidx.annotation_annotation",
],
+
+ manifest: "AndroidManifest.xml",
kotlincflags: ["-Xjvm-default=all"],
- test_suites: ["general-tests"],
}
diff --git a/packages/SystemUI/animation/TEST_MAPPING b/packages/SystemUI/animation/TEST_MAPPING
deleted file mode 100644
index 3dc8510bbf24..000000000000
--- a/packages/SystemUI/animation/TEST_MAPPING
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "presubmit": [
- {
- "name": "SystemUIAnimationLibTests"
- }
- ]
-}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt
index 43bfa74119b3..0e2d23b04a4f 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt
@@ -195,8 +195,16 @@ class RemoteTransitionAdapter {
val out = ArrayList<RemoteAnimationTarget>()
for (i in info.changes.indices) {
val change = info.changes[i]
- val changeIsWallpaper = change.flags and TransitionInfo.FLAG_IS_WALLPAPER != 0
- if (wallpapers != changeIsWallpaper) continue
+ if (change.hasFlags(TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)) {
+ // For embedded container, when the parent Task is also in the transition, we
+ // should only animate the parent Task.
+ if (change.parent != null) continue
+ // For embedded container without parent, we should only animate if it fills
+ // the Task. Otherwise we may animate only partial of the Task.
+ if (!change.hasFlags(TransitionInfo.FLAG_FILLS_TASK)) continue
+ }
+ // Check if it is wallpaper
+ if (wallpapers != change.hasFlags(TransitionInfo.FLAG_IS_WALLPAPER)) continue
out.add(createTarget(change, info.changes.size - i, info, t))
if (leashMap != null) {
leashMap[change.leash] = out[out.size - 1].leash
diff --git a/packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/MultiRippleController.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/MultiRippleController.kt
index 93e78acc63fd..93e78acc63fd 100644
--- a/packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/MultiRippleController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/MultiRippleController.kt
diff --git a/packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/MultiRippleView.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/MultiRippleView.kt
index f558fee776e6..b8dc223af56c 100644
--- a/packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/MultiRippleView.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/MultiRippleView.kt
@@ -22,6 +22,7 @@ import android.graphics.Paint
import android.util.AttributeSet
import android.util.Log
import android.view.View
+import androidx.annotation.VisibleForTesting
/**
* A view that allows multiple ripples to play.
@@ -30,7 +31,8 @@ import android.view.View
*/
class MultiRippleView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
- internal val ripples = ArrayList<RippleAnimation>()
+ @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
+ val ripples = ArrayList<RippleAnimation>()
private val listeners = ArrayList<RipplesFinishedListener>()
private val ripplePaint = Paint()
private var isWarningLogged = false
diff --git a/packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt
index b2f8994ebf51..0e3d41c40c97 100644
--- a/packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt
@@ -19,11 +19,13 @@ package com.android.systemui.surfaceeffects.ripple
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator
+import androidx.annotation.VisibleForTesting
import androidx.core.graphics.ColorUtils
/** A single ripple animation. */
class RippleAnimation(private val config: RippleAnimationConfig) {
- internal val rippleShader: RippleShader = RippleShader(config.rippleShape)
+ @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
+ val rippleShader: RippleShader = RippleShader(config.rippleShape)
private val animator: ValueAnimator = ValueAnimator.ofFloat(0f, 1f)
init {
diff --git a/packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationConfig.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationConfig.kt
index 773ac55130d4..773ac55130d4 100644
--- a/packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationConfig.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationConfig.kt
diff --git a/packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt
index a950d34513ee..f55fb97a9a97 100644
--- a/packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt
@@ -32,7 +32,7 @@ import com.android.systemui.surfaceeffects.shaderutil.ShaderUtilLibrary
*
* Modeled after frameworks/base/graphics/java/android/graphics/drawable/RippleShader.java.
*/
-class RippleShader internal constructor(rippleShape: RippleShape = RippleShape.CIRCLE) :
+class RippleShader(rippleShape: RippleShape = RippleShape.CIRCLE) :
RuntimeShader(buildShader(rippleShape)) {
/** Shapes that the [RippleShader] supports. */
diff --git a/packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt
index ae28a8b6dc2e..ae28a8b6dc2e 100644
--- a/packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt
diff --git a/packages/SystemUI/src/com/android/systemui/surfaceeffects/shaderutil/SdfShaderLibrary.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/SdfShaderLibrary.kt
index 8b2f46648fef..8b2f46648fef 100644
--- a/packages/SystemUI/src/com/android/systemui/surfaceeffects/shaderutil/SdfShaderLibrary.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/SdfShaderLibrary.kt
diff --git a/packages/SystemUI/src/com/android/systemui/surfaceeffects/shaderutil/ShaderUtilLibrary.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/ShaderUtilLibrary.kt
index d78e0c153db8..d78e0c153db8 100644
--- a/packages/SystemUI/src/com/android/systemui/surfaceeffects/shaderutil/ShaderUtilLibrary.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/ShaderUtilLibrary.kt
diff --git a/packages/SystemUI/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt
index 5ac3aad749fc..5ac3aad749fc 100644
--- a/packages/SystemUI/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt
diff --git a/packages/SystemUI/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseController.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseController.kt
index 4c7e5f4c7093..4c7e5f4c7093 100644
--- a/packages/SystemUI/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseController.kt
diff --git a/packages/SystemUI/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt
index 19c114d2693c..19c114d2693c 100644
--- a/packages/SystemUI/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt
diff --git a/packages/SystemUI/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt
index 8649d5924587..68712c6cd1de 100644
--- a/packages/SystemUI/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt
@@ -68,7 +68,7 @@ class TurbulenceNoiseView(context: Context?, attrs: AttributeSet?) : View(contex
canvas.drawPaint(paint)
}
- internal fun play(config: TurbulenceNoiseAnimationConfig) {
+ fun play(config: TurbulenceNoiseAnimationConfig) {
if (isPlaying) {
return // Ignore if the animation is playing.
}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
index 22944b8fba89..462b90a10aee 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
@@ -237,6 +237,28 @@ class AnimatableClockView @JvmOverloads constructor(
this.lockScreenColor = lockScreenColor
}
+ fun animateColorChange() {
+ logBuffer?.log(tag, DEBUG, "animateColorChange")
+ setTextStyle(
+ weight = lockScreenWeight,
+ textSize = -1f,
+ color = null, /* using current color */
+ animate = false,
+ duration = 0,
+ delay = 0,
+ onAnimationEnd = null
+ )
+ setTextStyle(
+ weight = lockScreenWeight,
+ textSize = -1f,
+ color = lockScreenColor,
+ animate = true,
+ duration = COLOR_ANIM_DURATION,
+ delay = 0,
+ onAnimationEnd = null
+ )
+ }
+
fun animateAppearOnLockscreen() {
logBuffer?.log(tag, DEBUG, "animateAppearOnLockscreen")
setTextStyle(
@@ -350,6 +372,7 @@ class AnimatableClockView @JvmOverloads constructor(
*
* By passing -1 to weight, the view preserves its current weight.
* By passing -1 to textSize, the view preserves its current text size.
+ * By passing null to color, the view preserves its current color.
*
* @param weight text weight.
* @param textSize font size.
@@ -611,6 +634,7 @@ class AnimatableClockView @JvmOverloads constructor(
private const val APPEAR_ANIM_DURATION: Long = 350
private const val CHARGE_ANIM_DURATION_PHASE_0: Long = 500
private const val CHARGE_ANIM_DURATION_PHASE_1: Long = 1000
+ private const val COLOR_ANIM_DURATION: Long = 400
// Constants for the animation
private val MOVE_INTERPOLATOR = Interpolators.EMPHASIZED
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
index e1f21742bf93..c540f0f7d557 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
@@ -142,7 +142,7 @@ class DefaultClockController(
currentColor = color
view.setColors(DOZE_COLOR, color)
if (!animations.dozeState.isActive) {
- view.animateAppearOnLockscreen()
+ view.animateColorChange()
}
}
}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/FakeKeyguardQuickAffordanceProviderClient.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/FakeKeyguardQuickAffordanceProviderClient.kt
index f490c5459d46..cb1a5f958ed6 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/FakeKeyguardQuickAffordanceProviderClient.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/FakeKeyguardQuickAffordanceProviderClient.kt
@@ -42,17 +42,17 @@ class FakeKeyguardQuickAffordanceProviderClient(
KeyguardQuickAffordanceProviderClient.Affordance(
id = AFFORDANCE_1,
name = AFFORDANCE_1,
- iconResourceId = 0,
+ iconResourceId = 1,
),
KeyguardQuickAffordanceProviderClient.Affordance(
id = AFFORDANCE_2,
name = AFFORDANCE_2,
- iconResourceId = 0,
+ iconResourceId = 2,
),
KeyguardQuickAffordanceProviderClient.Affordance(
id = AFFORDANCE_3,
name = AFFORDANCE_3,
- iconResourceId = 0,
+ iconResourceId = 3,
),
),
flags: List<KeyguardQuickAffordanceProviderClient.Flag> =
@@ -131,7 +131,12 @@ class FakeKeyguardQuickAffordanceProviderClient(
}
override suspend fun getAffordanceIcon(iconResourceId: Int, tintColor: Int): Drawable {
- return BitmapDrawable()
+ return when (iconResourceId) {
+ 1 -> ICON_1
+ 2 -> ICON_2
+ 3 -> ICON_3
+ else -> BitmapDrawable()
+ }
}
fun setFlag(
@@ -186,5 +191,8 @@ class FakeKeyguardQuickAffordanceProviderClient(
const val AFFORDANCE_1 = "affordance_1"
const val AFFORDANCE_2 = "affordance_2"
const val AFFORDANCE_3 = "affordance_3"
+ val ICON_1 = BitmapDrawable()
+ val ICON_2 = BitmapDrawable()
+ val ICON_3 = BitmapDrawable()
}
}
diff --git a/packages/SystemUI/res-keyguard/layout/fgs_footer.xml b/packages/SystemUI/res-keyguard/layout/fgs_footer.xml
deleted file mode 100644
index ee588f997ab8..000000000000
--- a/packages/SystemUI/res-keyguard/layout/fgs_footer.xml
+++ /dev/null
@@ -1,105 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2022 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<!-- TODO(b/242040009): Remove this file. -->
-<FrameLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="0dp"
- android:layout_height="@dimen/qs_security_footer_single_line_height"
- android:layout_weight="1"
- android:gravity="center"
- android:clickable="true"
- android:visibility="gone">
-
- <LinearLayout
- android:id="@+id/fgs_text_container"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_marginEnd="@dimen/qs_footer_action_inset"
- android:background="@drawable/qs_security_footer_background"
- android:layout_gravity="end"
- android:gravity="center"
- android:paddingHorizontal="@dimen/qs_footer_padding"
- >
-
- <ImageView
- android:id="@+id/primary_footer_icon"
- android:layout_width="@dimen/qs_footer_icon_size"
- android:layout_height="@dimen/qs_footer_icon_size"
- android:gravity="start"
- android:layout_marginEnd="12dp"
- android:contentDescription="@null"
- android:src="@drawable/ic_info_outline"
- android:tint="?android:attr/textColorSecondary" />
-
- <TextView
- android:id="@+id/footer_text"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:maxLines="1"
- android:ellipsize="end"
- android:textAppearance="@style/TextAppearance.QS.SecurityFooter"
- android:textColor="?android:attr/textColorSecondary"/>
-
- <ImageView
- android:id="@+id/fgs_new"
- android:layout_width="12dp"
- android:layout_height="12dp"
- android:scaleType="fitCenter"
- android:src="@drawable/fgs_dot"
- android:contentDescription="@string/fgs_dot_content_description"
- />
-
- <ImageView
- android:id="@+id/footer_icon"
- android:layout_width="@dimen/qs_footer_icon_size"
- android:layout_height="@dimen/qs_footer_icon_size"
- android:layout_marginStart="8dp"
- android:contentDescription="@null"
- android:src="@*android:drawable/ic_chevron_end"
- android:autoMirrored="true"
- android:tint="?android:attr/textColorSecondary" />
- </LinearLayout>
-
- <FrameLayout
- android:id="@+id/fgs_number_container"
- android:layout_width="@dimen/qs_footer_action_button_size"
- android:layout_height="@dimen/qs_footer_action_button_size"
- android:background="@drawable/qs_footer_action_circle"
- android:focusable="true"
- android:visibility="gone">
-
- <TextView
- android:id="@+id/fgs_number"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textAppearance="@style/TextAppearance.QS.SecurityFooter"
- android:layout_gravity="center"
- android:textColor="?android:attr/textColorPrimary"
- android:textSize="18sp"/>
- <ImageView
- android:id="@+id/fgs_collapsed_new"
- android:layout_width="12dp"
- android:layout_height="12dp"
- android:scaleType="fitCenter"
- android:layout_gravity="bottom|end"
- android:src="@drawable/fgs_dot"
- android:contentDescription="@string/fgs_dot_content_description"
- />
- </FrameLayout>
-
-</FrameLayout> \ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/layout/footer_actions.xml b/packages/SystemUI/res-keyguard/layout/footer_actions.xml
index 2261ae8d7714..4a2a1cb9dc6d 100644
--- a/packages/SystemUI/res-keyguard/layout/footer_actions.xml
+++ b/packages/SystemUI/res-keyguard/layout/footer_actions.xml
@@ -16,10 +16,8 @@
-->
<!-- Action buttons for footer in QS/QQS, containing settings button, power off button etc -->
-<!-- TODO(b/242040009): Clean up this file. -->
-<com.android.systemui.qs.FooterActionsView
+<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:layout_width="match_parent"
android:layout_height="@dimen/footer_actions_height"
android:elevation="@dimen/qs_panel_elevation"
@@ -28,74 +26,4 @@
android:background="@drawable/qs_footer_actions_background"
android:gravity="center_vertical|end"
android:layout_gravity="bottom"
->
-
- <LinearLayout
- android:id="@+id/security_footers_container"
- android:orientation="horizontal"
- android:layout_height="@dimen/qs_footer_action_button_size"
- android:layout_width="0dp"
- android:layout_weight="1"
- />
-
- <!-- Negative margin equal to -->
- <LinearLayout
- android:layout_height="match_parent"
- android:layout_width="wrap_content"
- android:layout_marginEnd="@dimen/qs_footer_action_inset_negative"
- >
-
- <com.android.systemui.statusbar.phone.MultiUserSwitch
- android:id="@id/multi_user_switch"
- android:layout_width="@dimen/qs_footer_action_button_size"
- android:layout_height="@dimen/qs_footer_action_button_size"
- android:background="@drawable/qs_footer_action_circle"
- android:focusable="true">
-
- <ImageView
- android:id="@+id/multi_user_avatar"
- android:layout_width="@dimen/qs_footer_icon_size"
- android:layout_height="@dimen/qs_footer_icon_size"
- android:layout_gravity="center"
- android:scaleType="centerInside" />
- </com.android.systemui.statusbar.phone.MultiUserSwitch>
-
- <com.android.systemui.statusbar.AlphaOptimizedFrameLayout
- android:id="@id/settings_button_container"
- android:layout_width="@dimen/qs_footer_action_button_size"
- android:layout_height="@dimen/qs_footer_action_button_size"
- android:background="@drawable/qs_footer_action_circle"
- android:clipChildren="false"
- android:clipToPadding="false">
-
- <com.android.systemui.statusbar.phone.SettingsButton
- android:id="@+id/settings_button"
- android:layout_width="@dimen/qs_footer_icon_size"
- android:layout_height="@dimen/qs_footer_icon_size"
- android:layout_gravity="center"
- android:background="@android:color/transparent"
- android:focusable="false"
- android:clickable="false"
- android:importantForAccessibility="yes"
- android:contentDescription="@string/accessibility_quick_settings_settings"
- android:scaleType="centerInside"
- android:src="@drawable/ic_settings"
- android:tint="?android:attr/textColorPrimary" />
-
- </com.android.systemui.statusbar.AlphaOptimizedFrameLayout>
-
- <com.android.systemui.statusbar.AlphaOptimizedImageView
- android:id="@id/pm_lite"
- android:layout_width="@dimen/qs_footer_action_button_size"
- android:layout_height="@dimen/qs_footer_action_button_size"
- android:background="@drawable/qs_footer_action_circle_color"
- android:clickable="true"
- android:clipToPadding="false"
- android:focusable="true"
- android:padding="@dimen/qs_footer_icon_padding"
- android:src="@*android:drawable/ic_lock_power_off"
- android:contentDescription="@string/accessibility_quick_settings_power_menu"
- android:tint="?androidprv:attr/textColorOnAccent" />
-
- </LinearLayout>
-</com.android.systemui.qs.FooterActionsView> \ No newline at end of file
+/> \ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher.xml b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher.xml
index 898935fc7e99..2cac9c706fe9 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher.xml
@@ -21,8 +21,6 @@
android:id="@+id/keyguard_bouncer_user_switcher"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:clipChildren="false"
- android:clipToPadding="false"
android:orientation="vertical"
android:gravity="center"
android:importantForAccessibility="yes"> <!-- Needed because TYPE_WINDOW_STATE_CHANGED is sent
diff --git a/packages/SystemUI/res-keyguard/values-nl/strings.xml b/packages/SystemUI/res-keyguard/values-nl/strings.xml
index 579824a2a734..57e5f8a49535 100644
--- a/packages/SystemUI/res-keyguard/values-nl/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-nl/strings.xml
@@ -74,7 +74,7 @@
<string name="kg_password_pin_failed" msgid="5136259126330604009">"Bewerking met pincode voor simkaart is mislukt."</string>
<string name="kg_password_puk_failed" msgid="6778867411556937118">"Bewerking met pukcode voor simkaart is mislukt."</string>
<string name="accessibility_ime_switch_button" msgid="9082358310194861329">"Invoermethode wijzigen"</string>
- <string name="airplane_mode" msgid="2528005343938497866">"Vliegtuigmodus"</string>
+ <string name="airplane_mode" msgid="2528005343938497866">"Vliegtuig­modus"</string>
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Patroon vereist nadat het apparaat opnieuw is opgestart"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Pincode vereist nadat het apparaat opnieuw is opgestart"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Wachtwoord vereist nadat het apparaat opnieuw is opgestart"</string>
diff --git a/packages/SystemUI/res/drawable/ic_circle_check_box.xml b/packages/SystemUI/res/drawable/ic_circle_check_box.xml
index b44a32dda172..00c10cecfe95 100644
--- a/packages/SystemUI/res/drawable/ic_circle_check_box.xml
+++ b/packages/SystemUI/res/drawable/ic_circle_check_box.xml
@@ -18,7 +18,7 @@
<item
android:id="@+id/checked"
android:state_checked="true"
- android:drawable="@drawable/media_output_status_check" />
+ android:drawable="@drawable/media_output_status_filled_checked" />
<item
android:id="@+id/unchecked"
android:state_checked="false"
diff --git a/packages/SystemUI/res/drawable/ic_note_task_button.xml b/packages/SystemUI/res/drawable/ic_note_task_button.xml
new file mode 100644
index 000000000000..bb5e224ea315
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_note_task_button.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportHeight="24"
+ android:viewportWidth="24">
+ <path
+ android:fillColor="#636C6F"
+ android:pathData="M17.6258,4.96L19.0358,6.37L7.4058,18.01L5.9958,16.6L17.6258,4.96ZM16.1358,3.62L4.1258,15.63L3.0158,19.83C2.9058,20.45 3.3858,21 3.9958,21C4.0558,21 4.1058,21 4.1658,20.99L8.3658,19.88L20.3758,7.86C20.7758,7.46 20.9958,6.93 20.9958,6.37C20.9958,5.81 20.7758,5.28 20.3758,4.88L19.1058,3.61C18.7158,3.22 18.1858,3 17.6258,3C17.0658,3 16.5358,3.22 16.1358,3.62Z" />
+ <path
+ android:fillColor="#636C6F"
+ android:fillType="evenOdd"
+ android:pathData="M20.1936,15.3369C20.3748,16.3837 19.9151,17.5414 18.8846,18.7597C19.1546,18.872 19.4576,18.9452 19.7724,18.9867C20.0839,19.0278 20.3683,19.0325 20.5749,19.0266C20.6772,19.0236 20.7578,19.0181 20.8101,19.0138C20.8362,19.0116 20.855,19.0097 20.8657,19.0085L20.8754,19.0074L20.875,19.0075C21.4217,18.9385 21.9214,19.325 21.9918,19.8718C22.0624,20.4195 21.6756,20.9208 21.1279,20.9914L21,19.9996C21.1279,20.9914 21.1265,20.9916 21.1265,20.9916L21.1249,20.9918L21.1211,20.9923L21.1107,20.9935L21.0795,20.997C21.0542,20.9998 21.0199,21.0032 20.9775,21.0067C20.8929,21.0138 20.7753,21.0216 20.6323,21.0257C20.3481,21.0339 19.9533,21.0279 19.5109,20.9695C18.873,20.8854 18.0393,20.6793 17.3106,20.1662C16.9605,20.3559 16.5876,20.4952 16.2299,20.6003C15.5742,20.7927 14.8754,20.8968 14.2534,20.9534C13.6801,21.0055 13.4553,21.0037 13.1015,21.0008C13.0689,21.0005 13.0352,21.0002 13,21H12.8594C12.8214,21.0002 12.785,21.0006 12.7504,21.0009C12.6524,21.0019 12.5683,21.0027 12.5,21H12.0562C12.0277,21.0003 12.0054,21.0006 11.9926,21.001L11.9751,21H9L11,19H11.9795C11.9929,18.9997 12.0064,18.9997 12.0199,19H12.4117C12.4534,18.9996 12.4864,18.9995 12.5,19H12.9675C12.977,18.9999 12.9878,18.9999 13,19C13.0446,19.0003 13.0859,19.0007 13.1249,19.0011C13.4259,19.0038 13.591,19.0054 14.0723,18.9616C14.6201,18.9118 15.1795,18.8242 15.6665,18.6813C15.753,18.6559 15.8346,18.6295 15.9114,18.6022C15.0315,17.2981 14.7125,16.1044 15.015,15.0829C15.4095,13.7511 16.6784,13.2418 17.7026,13.2864C18.7262,13.3309 19.954,13.9529 20.1936,15.3369ZM16.9327,15.6508C16.873,15.8523 16.8651,16.3878 17.4697,17.334C18.2007,16.4284 18.2585,15.8839 18.2229,15.6781C18.1939,15.5108 18.0297,15.3025 17.6157,15.2845C17.2025,15.2665 16.9885,15.4626 16.9327,15.6508Z" />
+</vector>
diff --git a/packages/SystemUI/res/drawable/media_output_dialog_seekbar_background.xml b/packages/SystemUI/res/drawable/media_output_dialog_seekbar_background.xml
index 55dce8f7e66c..43cf00332f63 100644
--- a/packages/SystemUI/res/drawable/media_output_dialog_seekbar_background.xml
+++ b/packages/SystemUI/res/drawable/media_output_dialog_seekbar_background.xml
@@ -17,7 +17,10 @@
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@android:id/background">
<shape>
- <corners android:radius="28dp" />
+ <corners
+ android:bottomRightRadius="28dp"
+ android:topRightRadius="28dp"
+ />
<solid android:color="@android:color/transparent" />
<size
android:height="64dp"/>
diff --git a/packages/SystemUI/res/drawable/media_output_icon_volume_off.xml b/packages/SystemUI/res/drawable/media_output_icon_volume_off.xml
new file mode 100644
index 000000000000..f29f44ce8226
--- /dev/null
+++ b/packages/SystemUI/res/drawable/media_output_icon_volume_off.xml
@@ -0,0 +1,27 @@
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="48dp"
+ android:height="48dp"
+ android:viewportWidth="48"
+ android:viewportHeight="48"
+ android:tint="?attr/colorControlNormal"
+ android:autoMirrored="true">
+ <path
+ android:fillColor="@color/media_dialog_item_main_content"
+ android:pathData="M40.65,45.2 L34.05,38.6Q32.65,39.6 31.025,40.325Q29.4,41.05 27.65,41.45V38.35Q28.8,38 29.875,37.575Q30.95,37.15 31.9,36.45L23.65,28.15V40L13.65,30H5.65V18H13.45L2.45,7L4.6,4.85L42.8,43ZM38.85,33.6 L36.7,31.45Q37.7,29.75 38.175,27.85Q38.65,25.95 38.65,23.95Q38.65,18.8 35.65,14.725Q32.65,10.65 27.65,9.55V6.45Q33.85,7.85 37.75,12.725Q41.65,17.6 41.65,23.95Q41.65,26.5 40.95,28.95Q40.25,31.4 38.85,33.6ZM32.15,26.9 L27.65,22.4V15.9Q30,17 31.325,19.2Q32.65,21.4 32.65,24Q32.65,24.75 32.525,25.475Q32.4,26.2 32.15,26.9ZM23.65,18.4 L18.45,13.2 23.65,8ZM20.65,32.7V25.2L16.45,21H8.65V27H14.95ZM18.55,23.1Z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/media_output_item_check_box.xml b/packages/SystemUI/res/drawable/media_output_item_check_box.xml
new file mode 100644
index 000000000000..a0742900e4c6
--- /dev/null
+++ b/packages/SystemUI/res/drawable/media_output_item_check_box.xml
@@ -0,0 +1,26 @@
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item
+ android:id="@+id/checked"
+ android:state_checked="true"
+ android:drawable="@drawable/media_output_status_checked" />
+ <item
+ android:id="@+id/unchecked"
+ android:state_checked="false"
+ android:drawable="@drawable/media_output_status_selectable" />
+</selector>
diff --git a/packages/SystemUI/res/drawable/media_output_status_checked.xml b/packages/SystemUI/res/drawable/media_output_status_checked.xml
new file mode 100644
index 000000000000..8f83ee2fa160
--- /dev/null
+++ b/packages/SystemUI/res/drawable/media_output_status_checked.xml
@@ -0,0 +1,26 @@
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M9.55,18 L3.85,12.3 5.275,10.875 9.55,15.15 18.725,5.975 20.15,7.4Z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/media_output_status_check.xml b/packages/SystemUI/res/drawable/media_output_status_filled_checked.xml
index 3d64f83eabe3..3d64f83eabe3 100644
--- a/packages/SystemUI/res/drawable/media_output_status_check.xml
+++ b/packages/SystemUI/res/drawable/media_output_status_filled_checked.xml
diff --git a/packages/SystemUI/res/drawable/media_output_status_selectable.xml b/packages/SystemUI/res/drawable/media_output_status_selectable.xml
new file mode 100644
index 000000000000..5465aa7300ab
--- /dev/null
+++ b/packages/SystemUI/res/drawable/media_output_status_selectable.xml
@@ -0,0 +1,26 @@
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M11,19V13H5V11H11V5H13V11H19V13H13V19Z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/media_output_title_icon_area.xml b/packages/SystemUI/res/drawable/media_output_title_icon_area.xml
new file mode 100644
index 000000000000..b93793773179
--- /dev/null
+++ b/packages/SystemUI/res/drawable/media_output_title_icon_area.xml
@@ -0,0 +1,25 @@
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <corners
+ android:bottomLeftRadius="28dp"
+ android:topLeftRadius="28dp"
+ android:bottomRightRadius="0dp"
+ android:topRightRadius="0dp"/>
+ <solid android:color="@color/media_dialog_item_background" />
+</shape> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/media_output_list_item_advanced.xml b/packages/SystemUI/res/layout/media_output_list_item_advanced.xml
new file mode 100644
index 000000000000..d49b9f105803
--- /dev/null
+++ b/packages/SystemUI/res/layout/media_output_list_item_advanced.xml
@@ -0,0 +1,156 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/device_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+ <FrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="64dp"
+ android:id="@+id/item_layout"
+ android:background="@drawable/media_output_item_background"
+ android:layout_marginStart="16dp"
+ android:layout_marginEnd="80dp"
+ android:layout_marginBottom="12dp">
+ <FrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="center_vertical|start">
+ <com.android.systemui.media.dialog.MediaOutputSeekbar
+ android:id="@+id/volume_seekbar"
+ android:splitTrack="false"
+ android:visibility="gone"
+ android:paddingStart="64dp"
+ android:paddingEnd="0dp"
+ android:background="@null"
+ android:contentDescription="@string/media_output_dialog_accessibility_seekbar"
+ android:progressDrawable="@drawable/media_output_dialog_seekbar_background"
+ android:thumb="@null"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"/>
+ </FrameLayout>
+
+ <FrameLayout
+ android:id="@+id/icon_area"
+ android:layout_width="64dp"
+ android:layout_height="64dp"
+ android:background="@drawable/media_output_title_icon_area"
+ android:layout_gravity="center_vertical|start">
+ <ImageView
+ android:id="@+id/title_icon"
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:animateLayoutChanges="true"
+ android:layout_gravity="center"/>
+ <TextView
+ android:id="@+id/volume_value"
+ android:animateLayoutChanges="true"
+ android:layout_gravity="center"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
+ android:textSize="16sp"
+ android:visibility="gone"/>
+ </FrameLayout>
+
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical|start"
+ android:layout_marginStart="72dp"
+ android:layout_marginEnd="56dp"
+ android:ellipsize="end"
+ android:maxLines="1"
+ android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
+ android:textSize="16sp"/>
+
+ <LinearLayout
+ android:id="@+id/two_line_layout"
+ android:orientation="vertical"
+ android:layout_width="wrap_content"
+ android:layout_gravity="center_vertical|start"
+ android:layout_height="48dp"
+ android:layout_marginEnd="56dp"
+ android:layout_marginStart="72dp">
+ <TextView
+ android:id="@+id/two_line_title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:ellipsize="end"
+ android:maxLines="1"
+ android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
+ android:textColor="@color/media_dialog_item_main_content"
+ android:textSize="16sp"/>
+ <TextView
+ android:id="@+id/subtitle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:ellipsize="end"
+ android:maxLines="1"
+ android:textColor="@color/media_dialog_item_main_content"
+ android:textSize="14sp"
+ android:fontFamily="@*android:string/config_bodyFontFamily"
+ android:visibility="gone"/>
+ </LinearLayout>
+
+ <ProgressBar
+ android:id="@+id/volume_indeterminate_progress"
+ style="?android:attr/progressBarStyleSmallTitle"
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:layout_marginEnd="16dp"
+ android:indeterminate="true"
+ android:layout_gravity="end|center"
+ android:indeterminateOnly="true"
+ android:visibility="gone"/>
+
+ <ImageView
+ android:id="@+id/media_output_item_status"
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:layout_marginEnd="16dp"
+ android:indeterminate="true"
+ android:layout_gravity="end|center"
+ android:indeterminateOnly="true"
+ android:importantForAccessibility="no"
+ android:visibility="gone"/>
+ </FrameLayout>
+ <FrameLayout
+ android:id="@+id/end_action_area"
+ android:layout_width="64dp"
+ android:layout_height="64dp"
+ android:visibility="gone"
+ android:layout_marginBottom="6dp"
+ android:layout_marginEnd="8dp"
+ android:layout_gravity="end|center"
+ android:gravity="center"
+ android:background="@drawable/media_output_item_background_active">
+ <CheckBox
+ android:id="@+id/check_box"
+ android:focusable="false"
+ android:importantForAccessibility="no"
+ android:layout_gravity="center"
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:button="@drawable/media_output_item_check_box"
+ android:visibility="gone"
+ />
+ </FrameLayout>
+</FrameLayout> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/media_session_view.xml b/packages/SystemUI/res/layout/media_session_view.xml
index 530db0d0304a..13c9a5ea05b1 100644
--- a/packages/SystemUI/res/layout/media_session_view.xml
+++ b/packages/SystemUI/res/layout/media_session_view.xml
@@ -35,7 +35,6 @@
android:layout_height="@dimen/qs_media_session_height_expanded"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:translationZ="0dp"
android:scaleType="centerCrop"
diff --git a/packages/SystemUI/res/layout/quick_settings_security_footer.xml b/packages/SystemUI/res/layout/quick_settings_security_footer.xml
deleted file mode 100644
index 194f3dd5dc26..000000000000
--- a/packages/SystemUI/res/layout/quick_settings_security_footer.xml
+++ /dev/null
@@ -1,61 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- 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.
--->
-<!-- TODO(b/242040009): Remove this file. -->
-<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="0dp"
- android:layout_height="@dimen/qs_security_footer_single_line_height"
- android:layout_weight="1"
- android:clickable="true"
- android:orientation="horizontal"
- android:paddingHorizontal="@dimen/qs_footer_padding"
- android:gravity="center_vertical"
- android:layout_gravity="center_vertical|center_horizontal"
- android:layout_marginEnd="@dimen/qs_footer_action_inset"
- android:background="@drawable/qs_security_footer_background"
- >
-
- <ImageView
- android:id="@+id/primary_footer_icon"
- android:layout_width="@dimen/qs_footer_icon_size"
- android:layout_height="@dimen/qs_footer_icon_size"
- android:gravity="start"
- android:layout_marginEnd="12dp"
- android:contentDescription="@null"
- android:tint="?android:attr/textColorSecondary" />
-
- <TextView
- android:id="@+id/footer_text"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:singleLine="true"
- android:ellipsize="end"
- android:textAppearance="@style/TextAppearance.QS.SecurityFooter"
- android:textColor="?android:attr/textColorSecondary"/>
-
- <ImageView
- android:id="@+id/footer_icon"
- android:layout_width="@dimen/qs_footer_icon_size"
- android:layout_height="@dimen/qs_footer_icon_size"
- android:layout_marginStart="8dp"
- android:contentDescription="@null"
- android:src="@*android:drawable/ic_chevron_end"
- android:autoMirrored="true"
- android:tint="?android:attr/textColorSecondary" />
-
-</LinearLayout>
diff --git a/packages/SystemUI/res/layout/status_bar_no_notifications.xml b/packages/SystemUI/res/layout/status_bar_no_notifications.xml
index a2abdb211602..856ba92b6d41 100644
--- a/packages/SystemUI/res/layout/status_bar_no_notifications.xml
+++ b/packages/SystemUI/res/layout/status_bar_no_notifications.xml
@@ -21,12 +21,29 @@
android:layout_height="wrap_content"
android:visibility="gone"
>
- <TextView
- android:id="@+id/no_notifications"
+ <LinearLayout android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:minHeight="64dp"
- android:textAppearance="?android:attr/textAppearanceButton"
+ android:layout_gravity="center"
android:gravity="center"
- android:text="@string/empty_shade_text"/>
+ >
+ <TextView
+ android:id="@+id/no_notifications"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="64dp"
+ android:gravity="center"
+ android:textAppearance="?android:attr/textAppearanceButton"
+ android:text="@string/empty_shade_text"/>
+ <TextView
+ android:id="@+id/no_notifications_footer"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:gravity="center"
+ android:drawablePadding="8dp"
+ android:visibility="gone"
+ android:textAppearance="?android:attr/textAppearanceButton"
+ android:text="@string/unlock_to_see_notif_text"/>
+ </LinearLayout>
</com.android.systemui.statusbar.EmptyShadeView>
diff --git a/packages/SystemUI/res/layout/super_notification_shade.xml b/packages/SystemUI/res/layout/super_notification_shade.xml
index bafdb11ab211..4abc1769ab54 100644
--- a/packages/SystemUI/res/layout/super_notification_shade.xml
+++ b/packages/SystemUI/res/layout/super_notification_shade.xml
@@ -98,16 +98,18 @@
android:singleLine="true"
android:ellipsize="marquee"
android:focusable="true" />
- <FrameLayout android:id="@+id/keyguard_bouncer_container"
- android:layout_height="0dp"
- android:layout_width="match_parent"
- android:layout_weight="1"
- android:background="@android:color/transparent"
- android:visibility="invisible"
- android:clipChildren="false"
- android:clipToPadding="false" />
</LinearLayout>
+ <FrameLayout android:id="@+id/keyguard_bouncer_container"
+ android:paddingTop="@dimen/status_bar_height"
+ android:layout_height="match_parent"
+ android:layout_width="match_parent"
+ android:layout_weight="1"
+ android:background="@android:color/transparent"
+ android:visibility="invisible"
+ android:clipChildren="false"
+ android:clipToPadding="false" />
+
<com.android.systemui.biometrics.AuthRippleView
android:id="@+id/auth_ripple"
android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/values-sw600dp/config.xml b/packages/SystemUI/res/values-sw600dp/config.xml
index 80628f903e76..f4434e8d0044 100644
--- a/packages/SystemUI/res/values-sw600dp/config.xml
+++ b/packages/SystemUI/res/values-sw600dp/config.xml
@@ -36,7 +36,4 @@
<integer name="qs_security_footer_maxLines">1</integer>
<bool name="config_use_large_screen_shade_header">true</bool>
-
- <!-- Whether to show the side fps hint while on bouncer -->
- <bool name="config_show_sidefps_hint_on_bouncer">true</bool>
</resources>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 8ee39dd5963f..70d53c73a4f7 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -133,6 +133,9 @@
<color name="biometric_dialog_accent">@color/material_dynamic_primary40</color>
<color name="biometric_dialog_error">#ffd93025</color> <!-- red 600 -->
+ <!-- SFPS colors -->
+ <color name="sfps_chevron_fill">@color/material_dynamic_primary90</color>
+
<!-- UDFPS colors -->
<color name="udfps_enroll_icon">#699FF3</color>
<color name="udfps_moving_target_fill">#C2D7F7</color>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index b8e2caf2a211..247e44d0f734 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -555,7 +555,7 @@
<string name="config_preferredEmergencySosPackage" translatable="false"></string>
<!-- Whether to show the side fps hint while on bouncer -->
- <bool name="config_show_sidefps_hint_on_bouncer">false</bool>
+ <bool name="config_show_sidefps_hint_on_bouncer">true</bool>
<!-- Whether to use the split 2-column notification shade -->
<bool name="config_use_split_notification_shade">false</bool>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 46e05fe652e3..73baeacc22c3 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -403,6 +403,8 @@
(quick_qs_offset_height (60dp) - ongoing_appops_chip_height (24dp) ) / 2 -->
<dimen name="notifications_top_padding_split_shade">18dp</dimen>
+ <dimen name="notifications_unseen_footer_icon_size">16dp</dimen>
+
<!-- Height of the status bar header bar when on Keyguard -->
<dimen name="status_bar_header_height_keyguard">40dp</dimen>
@@ -1220,6 +1222,8 @@
<dimen name="media_output_dialog_app_tier_icon_size">20dp</dimen>
<dimen name="media_output_dialog_background_radius">16dp</dimen>
<dimen name="media_output_dialog_active_background_radius">28dp</dimen>
+ <dimen name="media_output_dialog_default_margin_end">16dp</dimen>
+ <dimen name="media_output_dialog_selectable_margin_end">80dp</dimen>
<!-- Distance that the full shade transition takes in order to complete by tapping on a button
like "expand". -->
diff --git a/packages/SystemUI/res/values/flags.xml b/packages/SystemUI/res/values/flags.xml
index 49dd574af829..fd2e3249d59a 100644
--- a/packages/SystemUI/res/values/flags.xml
+++ b/packages/SystemUI/res/values/flags.xml
@@ -36,4 +36,8 @@
avatar will no longer show on the lockscreen -->
<bool name="flag_user_switcher_chip">false</bool>
+ <!-- Whether the battery icon is allowed to display a shield when battery life is being
+ protected. -->
+ <bool name="flag_battery_shield_icon">false</bool>
+
</resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 7597c6219011..c0d69d864013 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1105,6 +1105,12 @@
<!-- Text which is shown in the notification shade when there are no notifications. [CHAR LIMIT=30] -->
<string name="empty_shade_text">No notifications</string>
+ <!-- Text which is shown in the expanded notification shade when there are currently no notifications visible that the user hasn't already seen. [CHAR LIMIT=30] -->
+ <string name="no_unseen_notif_text">No new notifications</string>
+
+ <!-- Text which is shown in the locked notification shade when there are currently no notifications, but if the user were to unlock, notifications would appear. [CHAR LIMIT=40] -->
+ <string name="unlock_to_see_notif_text">Unlock to see older notifications</string>
+
<!-- Disclosure at the bottom of Quick Settings that indicates that parental controls are enabled. [CHAR LIMIT=100] -->
<string name="quick_settings_disclosure_parental_controls">This device is managed by your parent</string>
@@ -2475,7 +2481,7 @@
<!-- Controls menu, edit [CHAR_LIMIT=30] -->
<string name="controls_menu_edit">Edit controls</string>
- <!-- Title for the media output group dialog with media related devices [CHAR LIMIT=50] -->
+ <!-- Title for the media output dialog with media related devices [CHAR LIMIT=50] -->
<string name="media_output_dialog_add_output">Add outputs</string>
<!-- Title for the media output slice with group devices [CHAR LIMIT=50] -->
<string name="media_output_dialog_group">Group</string>
@@ -2499,6 +2505,8 @@
<string name="media_output_dialog_accessibility_title">Available devices for audio output.</string>
<!-- Accessibility text describing purpose of seekbar in media output dialog. [CHAR LIMIT=NONE] -->
<string name="media_output_dialog_accessibility_seekbar">Volume</string>
+ <!-- Summary for media output volume of a device in percentage [CHAR LIMIT=NONE] -->
+ <string name="media_output_dialog_volume_percentage"><xliff:g id="percentage" example="10">%1$d</xliff:g>%%</string>
<!-- Media Output Broadcast Dialog -->
<!-- Title for Broadcast First Notify Dialog [CHAR LIMIT=60] -->
@@ -2767,6 +2775,10 @@
<xliff:g id="weather_condition" example="Partly cloudy">%1$s</xliff:g>, <xliff:g id="temperature" example="7°C">%2$s</xliff:g>
</string>
+ <!-- TODO(b/259369672): Replace with final resource. -->
+ <!-- [CHAR LIMIT=30] Label used to open Note Task -->
+ <string name="note_task_button_label">Notetaking</string>
+
<!-- [CHAR LIMIT=NONE] Le audio broadcast dialog, media app is broadcasting -->
<string name="broadcasting_description_is_broadcasting">Broadcasting</string>
<!-- [CHAR LIMIT=NONE] Le audio broadcast dialog, title -->
diff --git a/packages/SystemUI/res/xml/combined_qs_header_scene.xml b/packages/SystemUI/res/xml/combined_qs_header_scene.xml
index de855e275f5f..c32de70771d0 100644
--- a/packages/SystemUI/res/xml/combined_qs_header_scene.xml
+++ b/packages/SystemUI/res/xml/combined_qs_header_scene.xml
@@ -124,20 +124,9 @@
</KeyFrameSet>
</Transition>
- <Transition
- android:id="@+id/large_screen_header_transition"
- app:constraintSetStart="@id/large_screen_header_constraint"
- app:constraintSetEnd="@id/large_screen_header_constraint"/>
-
- <!--
- Placeholder ConstraintSet. They are populated in the controller for this class.
- This is needed because there's no easy way to just refer to a `ConstraintSet` file. The
- options are either a layout file or inline the ConstraintSets.
- -->
- <ConstraintSet android:id="@id/qqs_header_constraint"/>
-
- <ConstraintSet android:id="@id/qs_header_constraint"/>
+ <Include app:constraintSet="@xml/large_screen_shade_header"/>
- <ConstraintSet android:id="@id/large_screen_header_constraint" />
+ <Include app:constraintSet="@xml/qs_header"/>
+ <Include app:constraintSet="@xml/qqs_header"/>
</MotionScene>
diff --git a/packages/SystemUI/res/xml/qqs_header.xml b/packages/SystemUI/res/xml/qqs_header.xml
index 5d3650ccc8e6..e56e5d557c2f 100644
--- a/packages/SystemUI/res/xml/qqs_header.xml
+++ b/packages/SystemUI/res/xml/qqs_header.xml
@@ -59,7 +59,6 @@
<Layout
android:layout_width="wrap_content"
android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
- app:layout_constrainedWidth="true"
app:layout_constraintHeight_min="@dimen/new_qs_header_non_clickable_element_height"
app:layout_constraintStart_toEndOf="@id/date"
app:layout_constraintEnd_toStartOf="@id/batteryRemainingIcon"
@@ -75,7 +74,6 @@
<Layout
android:layout_width="wrap_content"
android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
- app:layout_constrainedWidth="true"
app:layout_constraintHeight_min="@dimen/new_qs_header_non_clickable_element_height"
app:layout_constraintStart_toEndOf="@id/statusIcons"
app:layout_constraintEnd_toEndOf="@id/end_guide"
@@ -112,5 +110,4 @@
app:layout_constraintHorizontal_bias="1"
/>
</Constraint>
-
</ConstraintSet> \ No newline at end of file
diff --git a/packages/SystemUI/res/xml/qs_header.xml b/packages/SystemUI/res/xml/qs_header.xml
index 982c422f1fda..eca2b2acb079 100644
--- a/packages/SystemUI/res/xml/qs_header.xml
+++ b/packages/SystemUI/res/xml/qs_header.xml
@@ -56,6 +56,7 @@
<Layout
android:layout_width="wrap_content"
android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
+ app:layout_constrainedWidth="true"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/space"
app:layout_constraintBottom_toBottomOf="parent"
@@ -88,7 +89,6 @@
<Layout
android:layout_width="wrap_content"
android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
- app:layout_constrainedWidth="true"
app:layout_constraintStart_toEndOf="@id/space"
app:layout_constraintEnd_toStartOf="@id/batteryRemainingIcon"
app:layout_constraintTop_toTopOf="@id/date"
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt
index 6d0cc5ee4feb..738b37c9eea4 100644
--- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt
+++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt
@@ -34,7 +34,6 @@ import org.junit.runners.model.Statement
import platform.test.screenshot.DeviceEmulationRule
import platform.test.screenshot.DeviceEmulationSpec
import platform.test.screenshot.MaterialYouColorsRule
-import platform.test.screenshot.PathConfig
import platform.test.screenshot.ScreenshotTestRule
import platform.test.screenshot.getEmulatedDevicePathConfig
import platform.test.screenshot.matchers.BitmapMatcher
@@ -43,7 +42,6 @@ import platform.test.screenshot.matchers.BitmapMatcher
class ViewScreenshotTestRule(
emulationSpec: DeviceEmulationSpec,
private val matcher: BitmapMatcher = UnitTestBitmapMatcher,
- pathConfig: PathConfig = getEmulatedDevicePathConfig(emulationSpec),
assetsPathRelativeToBuildRoot: String
) : TestRule {
private val colorsRule = MaterialYouColorsRule()
diff --git a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt
index d172690e2488..d85292a90d63 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt
@@ -38,6 +38,7 @@ class FlagManager constructor(
const val ACTION_SET_FLAG = "com.android.systemui.action.SET_FLAG"
const val ACTION_GET_FLAGS = "com.android.systemui.action.GET_FLAGS"
const val FLAGS_PERMISSION = "com.android.systemui.permission.FLAGS"
+ const val ACTION_SYSUI_STARTED = "com.android.systemui.STARTED"
const val EXTRA_ID = "id"
const val EXTRA_VALUE = "value"
const val EXTRA_FLAGS = "flags"
diff --git a/packages/SystemUI/src/com/android/systemui/util/condition/CombinedCondition.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/condition/CombinedCondition.kt
index da81d540f189..2d83458ec2f7 100644
--- a/packages/SystemUI/src/com/android/systemui/util/condition/CombinedCondition.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/condition/CombinedCondition.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.util.condition
+package com.android.systemui.shared.condition
/**
* A higher order [Condition] which combines multiple conditions with a specified
diff --git a/packages/SystemUI/src/com/android/systemui/util/condition/Condition.java b/packages/SystemUI/shared/src/com/android/systemui/shared/condition/Condition.java
index b39adefa238b..cc48090e1217 100644
--- a/packages/SystemUI/src/com/android/systemui/util/condition/Condition.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/condition/Condition.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,13 +14,14 @@
* limitations under the License.
*/
-package com.android.systemui.util.condition;
+package com.android.systemui.shared.condition;
import android.util.Log;
-import com.android.systemui.statusbar.policy.CallbackController;
-
-import org.jetbrains.annotations.NotNull;
+import androidx.annotation.NonNull;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleEventObserver;
+import androidx.lifecycle.LifecycleOwner;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
@@ -33,7 +34,7 @@ import java.util.List;
* Base class for a condition that needs to be fulfilled in order for {@link Monitor} to inform
* its callbacks.
*/
-public abstract class Condition implements CallbackController<Condition.Callback> {
+public abstract class Condition {
private final String mTag = getClass().getSimpleName();
private final ArrayList<WeakReference<Callback>> mCallbacks = new ArrayList<>();
@@ -79,8 +80,7 @@ public abstract class Condition implements CallbackController<Condition.Callback
* Registers a callback to receive updates once started. This should be called before
* {@link #start()}. Also triggers the callback immediately if already started.
*/
- @Override
- public void addCallback(@NotNull Callback callback) {
+ public void addCallback(@NonNull Callback callback) {
if (shouldLog()) Log.d(mTag, "adding callback");
mCallbacks.add(new WeakReference<>(callback));
@@ -96,8 +96,7 @@ public abstract class Condition implements CallbackController<Condition.Callback
/**
* Removes the provided callback from further receiving updates.
*/
- @Override
- public void removeCallback(@NotNull Callback callback) {
+ public void removeCallback(@NonNull Callback callback) {
if (shouldLog()) Log.d(mTag, "removing callback");
final Iterator<WeakReference<Callback>> iterator = mCallbacks.iterator();
while (iterator.hasNext()) {
@@ -116,6 +115,29 @@ public abstract class Condition implements CallbackController<Condition.Callback
}
/**
+ * Wrapper to {@link #addCallback(Callback)} when a lifecycle is in the resumed state
+ * and {@link #removeCallback(Callback)} when not resumed automatically.
+ */
+ public Callback observe(LifecycleOwner owner, Callback listener) {
+ return observe(owner.getLifecycle(), listener);
+ }
+
+ /**
+ * Wrapper to {@link #addCallback(Callback)} when a lifecycle is in the resumed state
+ * and {@link #removeCallback(Condition.Callback)} when not resumed automatically.
+ */
+ public Callback observe(Lifecycle lifecycle, Callback listener) {
+ lifecycle.addObserver((LifecycleEventObserver) (lifecycleOwner, event) -> {
+ if (event == Lifecycle.Event.ON_RESUME) {
+ addCallback(listener);
+ } else if (event == Lifecycle.Event.ON_PAUSE) {
+ removeCallback(listener);
+ }
+ });
+ return listener;
+ }
+
+ /**
* Updates the value for whether the condition has been fulfilled, and sends an update if the
* value changes and any callback is registered.
*
@@ -187,7 +209,7 @@ public abstract class Condition implements CallbackController<Condition.Callback
* Creates a new condition which will only be true when both this condition and all the provided
* conditions are true.
*/
- public Condition and(Collection<Condition> others) {
+ public Condition and(@NonNull Collection<Condition> others) {
final List<Condition> conditions = new ArrayList<>(others);
conditions.add(this);
return new CombinedCondition(conditions, Evaluator.OP_AND);
@@ -197,7 +219,7 @@ public abstract class Condition implements CallbackController<Condition.Callback
* Creates a new condition which will only be true when both this condition and the provided
* condition is true.
*/
- public Condition and(Condition other) {
+ public Condition and(@NonNull Condition other) {
return new CombinedCondition(Arrays.asList(this, other), Evaluator.OP_AND);
}
@@ -205,7 +227,7 @@ public abstract class Condition implements CallbackController<Condition.Callback
* Creates a new condition which will only be true when either this condition or any of the
* provided conditions are true.
*/
- public Condition or(Collection<Condition> others) {
+ public Condition or(@NonNull Collection<Condition> others) {
final List<Condition> conditions = new ArrayList<>(others);
conditions.add(this);
return new CombinedCondition(conditions, Evaluator.OP_OR);
@@ -215,7 +237,7 @@ public abstract class Condition implements CallbackController<Condition.Callback
* Creates a new condition which will only be true when either this condition or the provided
* condition is true.
*/
- public Condition or(Condition other) {
+ public Condition or(@NonNull Condition other) {
return new CombinedCondition(Arrays.asList(this, other), Evaluator.OP_OR);
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/condition/Evaluator.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/condition/Evaluator.kt
index cf44e84a563f..23742c503ed3 100644
--- a/packages/SystemUI/src/com/android/systemui/util/condition/Evaluator.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/condition/Evaluator.kt
@@ -1,4 +1,20 @@
-package com.android.systemui.util.condition
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shared.condition
import android.annotation.IntDef
diff --git a/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java b/packages/SystemUI/shared/src/com/android/systemui/shared/condition/Monitor.java
index 24bc9078475f..95675cef9136 100644
--- a/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/condition/Monitor.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,14 +14,14 @@
* limitations under the License.
*/
-package com.android.systemui.util.condition;
+package com.android.systemui.shared.condition;
import android.util.ArraySet;
import android.util.Log;
-import com.android.systemui.dagger.qualifiers.Main;
+import androidx.annotation.NonNull;
-import org.jetbrains.annotations.NotNull;
+import com.android.systemui.dagger.qualifiers.Main;
import java.util.ArrayList;
import java.util.Collections;
@@ -100,7 +100,7 @@ public class Monitor {
* @param subscription A {@link Subscription} detailing the desired conditions and callback.
* @return A {@link Subscription.Token} that can be used to remove the subscription.
*/
- public Subscription.Token addSubscription(@NotNull Subscription subscription) {
+ public Subscription.Token addSubscription(@NonNull Subscription subscription) {
final Subscription.Token token = new Subscription.Token();
final SubscriptionState state = new SubscriptionState(subscription);
@@ -131,7 +131,7 @@ public class Monitor {
* @param token The {@link Subscription.Token} returned when the {@link Subscription} was
* originally added.
*/
- public void removeSubscription(@NotNull Subscription.Token token) {
+ public void removeSubscription(@NonNull Subscription.Token token) {
mExecutor.execute(() -> {
if (shouldLog()) Log.d(mTag, "removing subscription");
if (!mSubscriptions.containsKey(token)) {
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java
index c9ea79432360..5883b6c0e723 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java
@@ -87,8 +87,8 @@ public class PreviewPositionHelper {
taskPercent = mDesiredStagePosition != STAGE_POSITION_TOP_OR_LEFT
? mSplitBounds.topTaskPercent
: (1 - (mSplitBounds.topTaskPercent + mSplitBounds.dividerHeightPercent));
- // Scale portrait height to that of (actual screen - taskbar inset)
- fullscreenTaskHeight = (screenHeightPx) * taskPercent;
+ // Scale portrait height to that of the actual screen
+ fullscreenTaskHeight = screenHeightPx * taskPercent;
if (mTaskbarInApp) {
canvasScreenRatio = canvasHeight / fullscreenTaskHeight;
} else {
diff --git a/packages/SystemUI/src/com/android/keyguard/FaceAuthReason.kt b/packages/SystemUI/src/com/android/keyguard/FaceAuthReason.kt
index 4a41b3fe2589..5bb9367fa4a5 100644
--- a/packages/SystemUI/src/com/android/keyguard/FaceAuthReason.kt
+++ b/packages/SystemUI/src/com/android/keyguard/FaceAuthReason.kt
@@ -48,11 +48,13 @@ import com.android.keyguard.InternalFaceAuthReasons.KEYGUARD_INIT
import com.android.keyguard.InternalFaceAuthReasons.KEYGUARD_OCCLUSION_CHANGED
import com.android.keyguard.InternalFaceAuthReasons.KEYGUARD_RESET
import com.android.keyguard.InternalFaceAuthReasons.KEYGUARD_VISIBILITY_CHANGED
+import com.android.keyguard.InternalFaceAuthReasons.NON_STRONG_BIOMETRIC_ALLOWED_CHANGED
import com.android.keyguard.InternalFaceAuthReasons.OCCLUDING_APP_REQUESTED
import com.android.keyguard.InternalFaceAuthReasons.PRIMARY_BOUNCER_SHOWN
import com.android.keyguard.InternalFaceAuthReasons.PRIMARY_BOUNCER_SHOWN_OR_WILL_BE_SHOWN
import com.android.keyguard.InternalFaceAuthReasons.RETRY_AFTER_HW_UNAVAILABLE
import com.android.keyguard.InternalFaceAuthReasons.STARTED_WAKING_UP
+import com.android.keyguard.InternalFaceAuthReasons.STRONG_AUTH_ALLOWED_CHANGED
import com.android.keyguard.InternalFaceAuthReasons.TRUST_DISABLED
import com.android.keyguard.InternalFaceAuthReasons.TRUST_ENABLED
import com.android.keyguard.InternalFaceAuthReasons.USER_SWITCHING
@@ -121,6 +123,9 @@ private object InternalFaceAuthReasons {
const val FACE_AUTHENTICATED = "Face auth started/stopped because face is authenticated"
const val BIOMETRIC_ENABLED =
"Face auth started/stopped because biometric is enabled on keyguard"
+ const val STRONG_AUTH_ALLOWED_CHANGED = "Face auth stopped because strong auth allowed changed"
+ const val NON_STRONG_BIOMETRIC_ALLOWED_CHANGED =
+ "Face auth stopped because non strong biometric allowed changed"
}
/**
@@ -204,7 +209,11 @@ constructor(private val id: Int, val reason: String, var extraInfo: Int = 0) :
@UiEvent(doc = FACE_AUTHENTICATED)
FACE_AUTH_UPDATED_ON_FACE_AUTHENTICATED(1187, FACE_AUTHENTICATED),
@UiEvent(doc = BIOMETRIC_ENABLED)
- FACE_AUTH_UPDATED_BIOMETRIC_ENABLED_ON_KEYGUARD(1188, BIOMETRIC_ENABLED);
+ FACE_AUTH_UPDATED_BIOMETRIC_ENABLED_ON_KEYGUARD(1188, BIOMETRIC_ENABLED),
+ @UiEvent(doc = STRONG_AUTH_ALLOWED_CHANGED)
+ FACE_AUTH_UPDATED_STRONG_AUTH_CHANGED(1255, STRONG_AUTH_ALLOWED_CHANGED),
+ @UiEvent(doc = NON_STRONG_BIOMETRIC_ALLOWED_CHANGED)
+ FACE_AUTH_NON_STRONG_BIOMETRIC_ALLOWED_CHANGED(1256, NON_STRONG_BIOMETRIC_ALLOWED_CHANGED);
override fun getId(): Int = this.id
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
index 860c8e3a9f77..7da27b1d6898 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
@@ -260,7 +260,8 @@ public abstract class KeyguardAbsKeyInputViewController<T extends KeyguardAbsKey
if (reason != PROMPT_REASON_NONE) {
int promtReasonStringRes = mView.getPromptReasonStringRes(reason);
if (promtReasonStringRes != 0) {
- mMessageAreaController.setMessage(promtReasonStringRes);
+ mMessageAreaController.setMessage(
+ mView.getResources().getString(promtReasonStringRes), false);
}
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
index 2e9ad5868eba..53b569af29e1 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
@@ -143,7 +143,9 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView>
public void startAppearAnimation() {
if (TextUtils.isEmpty(mMessageAreaController.getMessage())) {
- mMessageAreaController.setMessage(getInitialMessageResId());
+ mMessageAreaController.setMessage(
+ mView.getResources().getString(getInitialMessageResId()),
+ /* animate= */ false);
}
mView.startAppearAnimation();
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
index e6283b86283b..52ca1668e4c9 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
@@ -53,17 +53,17 @@ data class KeyguardFaceListenModel(
val biometricSettingEnabledForUser: Boolean,
val bouncerFullyShown: Boolean,
val faceAndFpNotAuthenticated: Boolean,
+ val faceAuthAllowed: Boolean,
val faceDisabled: Boolean,
val faceLockedOut: Boolean,
- val fpLockedOut: Boolean,
val goingToSleep: Boolean,
val keyguardAwake: Boolean,
val keyguardGoingAway: Boolean,
val listeningForFaceAssistant: Boolean,
val occludingAppRequestingFaceAuth: Boolean,
val primaryUser: Boolean,
- val scanningAllowedByStrongAuth: Boolean,
val secureCameraLaunched: Boolean,
+ val supportsDetect: Boolean,
val switchingUser: Boolean,
val udfpsBouncerShowing: Boolean,
val udfpsFingerDown: Boolean,
@@ -79,9 +79,8 @@ data class KeyguardActiveUnlockModel(
// keep sorted
val awakeKeyguard: Boolean,
val authInterruptActive: Boolean,
- val encryptedOrTimedOut: Boolean,
- val fpLockout: Boolean,
- val lockDown: Boolean,
+ val fpLockedOut: Boolean,
+ val primaryAuthRequired: Boolean,
val switchingUser: Boolean,
val triggerActiveUnlockForAssistant: Boolean,
val userCanDismissLockScreen: Boolean
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
index 5d86ccd5409e..67e3400670ba 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
@@ -52,6 +52,7 @@ public class KeyguardPINView extends KeyguardPinBasedInputView {
private int mYTransOffset;
private View mBouncerMessageView;
@DevicePostureInt private int mLastDevicePosture = DEVICE_POSTURE_UNKNOWN;
+ public static final long ANIMATION_DURATION = 650;
public KeyguardPINView(Context context) {
this(context, null);
@@ -181,7 +182,7 @@ public class KeyguardPINView extends KeyguardPinBasedInputView {
if (mAppearAnimator.isRunning()) {
mAppearAnimator.cancel();
}
- mAppearAnimator.setDuration(650);
+ mAppearAnimator.setDuration(ANIMATION_DURATION);
mAppearAnimator.addUpdateListener(animation -> animate(animation.getAnimatedFraction()));
mAppearAnimator.start();
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
index 2cc5ccdc3fa1..c985fd7bef82 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
@@ -34,6 +34,7 @@ import android.graphics.Insets;
import android.graphics.Rect;
import android.os.Trace;
import android.util.AttributeSet;
+import android.view.WindowInsets;
import android.view.WindowInsetsAnimationControlListener;
import android.view.WindowInsetsAnimationController;
import android.view.animation.AnimationUtils;
@@ -236,4 +237,50 @@ public class KeyguardPasswordView extends KeyguardAbsKeyInputView {
return getResources().getString(
com.android.internal.R.string.keyguard_accessibility_password_unlock);
}
+
+ @Override
+ public WindowInsets onApplyWindowInsets(WindowInsets insets) {
+ if (!mPasswordEntry.isFocused() && isVisibleToUser()) {
+ mPasswordEntry.requestFocus();
+ }
+ return super.onApplyWindowInsets(insets);
+ }
+
+ @Override
+ public void onWindowFocusChanged(boolean hasWindowFocus) {
+ super.onWindowFocusChanged(hasWindowFocus);
+ if (hasWindowFocus) {
+ if (isVisibleToUser()) {
+ showKeyboard();
+ } else {
+ hideKeyboard();
+ }
+ }
+ }
+
+ /**
+ * Sends signal to the focused window to show the keyboard.
+ */
+ public void showKeyboard() {
+ post(() -> {
+ if (mPasswordEntry.isAttachedToWindow()
+ && !mPasswordEntry.getRootWindowInsets().isVisible(WindowInsets.Type.ime())) {
+ mPasswordEntry.requestFocus();
+ mPasswordEntry.getWindowInsetsController().show(WindowInsets.Type.ime());
+ }
+ });
+ }
+
+ /**
+ * Sends signal to the focused window to hide the keyboard.
+ */
+ public void hideKeyboard() {
+ post(() -> {
+ if (mPasswordEntry.isAttachedToWindow()
+ && mPasswordEntry.getRootWindowInsets().isVisible(WindowInsets.Type.ime())) {
+ mPasswordEntry.clearFocus();
+ mPasswordEntry.getWindowInsetsController().hide(WindowInsets.Type.ime());
+ }
+ });
+ }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
index 195e8f92754d..d221e22a4fcd 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
@@ -26,7 +26,6 @@ import android.text.method.TextKeyListener;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup.MarginLayoutParams;
-import android.view.WindowInsets;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodManager;
@@ -200,12 +199,9 @@ public class KeyguardPasswordViewController
return;
}
- mView.post(() -> {
- if (mView.isShown()) {
- mPasswordEntry.requestFocus();
- mPasswordEntry.getWindowInsetsController().show(WindowInsets.Type.ime());
- }
- });
+ if (mView.isShown()) {
+ mView.showKeyboard();
+ }
}
@Override
@@ -227,16 +223,12 @@ public class KeyguardPasswordViewController
super.onPause();
});
}
- if (mPasswordEntry.isAttachedToWindow()) {
- mPasswordEntry.getWindowInsetsController().hide(WindowInsets.Type.ime());
- }
+ mView.hideKeyboard();
}
@Override
public void onStartingToHide() {
- if (mPasswordEntry.isAttachedToWindow()) {
- mPasswordEntry.getWindowInsetsController().hide(WindowInsets.Type.ime());
- }
+ mView.hideKeyboard();
}
private void updateSwitchImeButton() {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 5c4126eeb93a..5d7a6f122e69 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -36,8 +36,11 @@ import static com.android.systemui.plugins.FalsingManager.LOW_PENALTY;
import static java.lang.Integer.max;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.admin.DevicePolicyManager;
@@ -221,10 +224,11 @@ public class KeyguardSecurityContainer extends ConstraintLayout {
public void onEnd(WindowInsetsAnimation animation) {
if (!mDisappearAnimRunning) {
endJankInstrument(InteractionJankMonitor.CUJ_LOCKSCREEN_PASSWORD_APPEAR);
- updateChildren(0 /* translationY */, 1f /* alpha */);
} else {
endJankInstrument(InteractionJankMonitor.CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR);
+ setAlpha(0f);
}
+ updateChildren(0 /* translationY */, 1f /* alpha */);
}
private void updateChildren(int translationY, float alpha) {
@@ -966,11 +970,23 @@ public class KeyguardSecurityContainer extends ConstraintLayout {
}
mUserSwitcherViewGroup.setAlpha(0f);
- ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(mUserSwitcherViewGroup, View.ALPHA,
- 1f);
- alphaAnim.setInterpolator(Interpolators.ALPHA_IN);
- alphaAnim.setDuration(500);
- alphaAnim.start();
+ ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
+ int yTrans = mView.getResources().getDimensionPixelSize(R.dimen.pin_view_trans_y_entry);
+ animator.setInterpolator(Interpolators.STANDARD_DECELERATE);
+ animator.setDuration(650);
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mUserSwitcherViewGroup.setAlpha(1f);
+ mUserSwitcherViewGroup.setTranslationY(0f);
+ }
+ });
+ animator.addUpdateListener(animation -> {
+ float value = (float) animation.getAnimatedValue();
+ mUserSwitcherViewGroup.setAlpha(value);
+ mUserSwitcherViewGroup.setTranslationY(yTrans - yTrans * value);
+ });
+ animator.start();
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 64fa15e0cd20..84ef505c0af9 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -34,9 +34,9 @@ import static android.os.BatteryManager.BATTERY_STATUS_UNKNOWN;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT;
-import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
import static com.android.keyguard.FaceAuthReasonKt.apiRequestReasonToUiEvent;
+import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_NON_STRONG_BIOMETRIC_ALLOWED_CHANGED;
import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_STOPPED_DREAM_STARTED;
import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_STOPPED_FACE_CANCEL_NOT_RECEIVED;
import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_STOPPED_FINISHED_GOING_TO_SLEEP;
@@ -65,6 +65,7 @@ import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_ON_FACE_AUT
import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_ON_KEYGUARD_INIT;
import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_PRIMARY_BOUNCER_SHOWN;
import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_STARTED_WAKING_UP;
+import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_STRONG_AUTH_CHANGED;
import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_USER_SWITCHING;
import static com.android.systemui.DejankUtils.whitelistIpcs;
@@ -170,7 +171,6 @@ import java.util.Map.Entry;
import java.util.Set;
import java.util.TimeZone;
import java.util.concurrent.Executor;
-import java.util.function.Consumer;
import java.util.stream.Collectors;
import javax.inject.Inject;
@@ -382,6 +382,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
private static final int HAL_ERROR_RETRY_MAX = 20;
@VisibleForTesting
+ protected static final int HAL_POWER_PRESS_TIMEOUT = 50; // ms
+
+ @VisibleForTesting
protected final Runnable mFpCancelNotReceived = this::onFingerprintCancelNotReceived;
private final Runnable mFaceCancelNotReceived = this::onFaceCancelNotReceived;
@@ -902,7 +905,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
}
- private final Runnable mRetryFingerprintAuthentication = new Runnable() {
+ private final Runnable mRetryFingerprintAuthenticationAfterHwUnavailable = new Runnable() {
@SuppressLint("MissingPermission")
@Override
public void run() {
@@ -911,7 +914,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
} else if (mHardwareFingerprintUnavailableRetryCount < HAL_ERROR_RETRY_MAX) {
mHardwareFingerprintUnavailableRetryCount++;
- mHandler.postDelayed(mRetryFingerprintAuthentication, HAL_ERROR_RETRY_TIMEOUT);
+ mHandler.postDelayed(mRetryFingerprintAuthenticationAfterHwUnavailable,
+ HAL_ERROR_RETRY_TIMEOUT);
}
}
};
@@ -939,10 +943,18 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
setFingerprintRunningState(BIOMETRIC_STATE_STOPPED);
}
- if (msgId == FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE
- || msgId == FingerprintManager.BIOMETRIC_ERROR_POWER_PRESSED) {
- mLogger.logRetryAfterFpError(msgId, errString);
- mHandler.postDelayed(mRetryFingerprintAuthentication, HAL_ERROR_RETRY_TIMEOUT);
+ if (msgId == FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE) {
+ mLogger.logRetryAfterFpErrorWithDelay(msgId, errString, HAL_ERROR_RETRY_TIMEOUT);
+ mHandler.postDelayed(mRetryFingerprintAuthenticationAfterHwUnavailable,
+ HAL_ERROR_RETRY_TIMEOUT);
+ }
+
+ if (msgId == FingerprintManager.BIOMETRIC_ERROR_POWER_PRESSED) {
+ mLogger.logRetryAfterFpErrorWithDelay(msgId, errString, HAL_POWER_PRESS_TIMEOUT);
+ mHandler.postDelayed(() -> {
+ mLogger.d("Retrying fingerprint listening after power pressed error.");
+ updateFingerprintListeningState(BIOMETRIC_ACTION_START);
+ }, HAL_POWER_PRESS_TIMEOUT);
}
boolean lockedOutStateChanged = false;
@@ -1401,6 +1413,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
}
+ /**
+ * Whether the user locked down the device. This doesn't include device policy manager lockdown.
+ */
public boolean isUserInLockdown(int userId) {
return containsFlag(mStrongAuthTracker.getStrongAuthForUser(userId),
STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
@@ -1432,7 +1447,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
return mStrongAuthTracker;
}
- private void notifyStrongAuthStateChanged(int userId) {
+ @VisibleForTesting
+ void notifyStrongAuthAllowedChanged(int userId) {
Assert.isMainThread();
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
@@ -1440,6 +1456,15 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
cb.onStrongAuthStateChanged(userId);
}
}
+ if (userId == getCurrentUser()) {
+ FACE_AUTH_UPDATED_STRONG_AUTH_CHANGED.setExtraInfo(
+ mStrongAuthTracker.getStrongAuthForUser(getCurrentUser()));
+
+ // Strong auth is only reset when primary auth is used to enter the device,
+ // so we only check whether to stop biometric listening states here
+ updateBiometricListeningState(
+ BIOMETRIC_ACTION_STOP, FACE_AUTH_UPDATED_STRONG_AUTH_CHANGED);
+ }
}
private void notifyLockedOutStateChanged(BiometricSourceType type) {
@@ -1451,8 +1476,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
}
}
-
- private void notifyNonStrongBiometricStateChanged(int userId) {
+ @VisibleForTesting
+ void notifyNonStrongBiometricAllowedChanged(int userId) {
Assert.isMainThread();
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
@@ -1460,6 +1485,16 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
cb.onNonStrongBiometricAllowedChanged(userId);
}
}
+ if (userId == getCurrentUser()) {
+ FACE_AUTH_NON_STRONG_BIOMETRIC_ALLOWED_CHANGED.setExtraInfo(
+ mStrongAuthTracker.isNonStrongBiometricAllowedAfterIdleTimeout(
+ getCurrentUser()) ? -1 : 1);
+
+ // This is only reset when primary auth is used to enter the device, so we only check
+ // whether to stop biometric listening states here
+ updateBiometricListeningState(BIOMETRIC_ACTION_STOP,
+ FACE_AUTH_NON_STRONG_BIOMETRIC_ALLOWED_CHANGED);
+ }
}
private void dispatchErrorMessage(CharSequence message) {
@@ -1805,16 +1840,12 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
}
- public static class StrongAuthTracker extends LockPatternUtils.StrongAuthTracker {
- private final Consumer<Integer> mStrongAuthRequiredChangedCallback;
- private final Consumer<Integer> mNonStrongBiometricAllowedChanged;
-
- public StrongAuthTracker(Context context,
- Consumer<Integer> strongAuthRequiredChangedCallback,
- Consumer<Integer> nonStrongBiometricAllowedChanged) {
+ /**
+ * Updates callbacks when strong auth requirements change.
+ */
+ public class StrongAuthTracker extends LockPatternUtils.StrongAuthTracker {
+ public StrongAuthTracker(Context context) {
super(context);
- mStrongAuthRequiredChangedCallback = strongAuthRequiredChangedCallback;
- mNonStrongBiometricAllowedChanged = nonStrongBiometricAllowedChanged;
}
public boolean isUnlockingWithBiometricAllowed(boolean isStrongBiometric) {
@@ -1830,7 +1861,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
@Override
public void onStrongAuthRequiredChanged(int userId) {
- mStrongAuthRequiredChangedCallback.accept(userId);
+ notifyStrongAuthAllowedChanged(userId);
}
// TODO(b/247091681): Renaming the inappropriate onIsNonStrongBiometricAllowedChanged
@@ -1838,7 +1869,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
// Strong-Auth
@Override
public void onIsNonStrongBiometricAllowedChanged(int userId) {
- mNonStrongBiometricAllowedChanged.accept(userId);
+ notifyNonStrongBiometricAllowedChanged(userId);
}
}
@@ -1999,8 +2030,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
mUserTracker = userTracker;
mTelephonyListenerManager = telephonyListenerManager;
mDeviceProvisioned = isDeviceProvisionedInSettingsDb();
- mStrongAuthTracker = new StrongAuthTracker(context, this::notifyStrongAuthStateChanged,
- this::notifyNonStrongBiometricStateChanged);
+ mStrongAuthTracker = new StrongAuthTracker(context);
mBackgroundExecutor = backgroundExecutor;
mBroadcastDispatcher = broadcastDispatcher;
mInteractionJankMonitor = interactionJankMonitor;
@@ -2575,24 +2605,17 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
|| !mLockPatternUtils.isSecure(user);
// Don't trigger active unlock if fp is locked out
- final boolean fpLockedout = mFingerprintLockedOut || mFingerprintLockedOutPermanent;
+ final boolean fpLockedOut = mFingerprintLockedOut || mFingerprintLockedOutPermanent;
// Don't trigger active unlock if primary auth is required
- final int strongAuth = mStrongAuthTracker.getStrongAuthForUser(user);
- final boolean isLockDown =
- containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW)
- || containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
- final boolean isEncryptedOrTimedOut =
- containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_BOOT)
- || containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_TIMEOUT);
+ final boolean primaryAuthRequired = !isUnlockingWithBiometricAllowed(true);
final boolean shouldTriggerActiveUnlock =
(mAuthInterruptActive || triggerActiveUnlockForAssistant || awakeKeyguard)
&& !mSwitchingUser
&& !userCanDismissLockScreen
- && !fpLockedout
- && !isLockDown
- && !isEncryptedOrTimedOut
+ && !fpLockedOut
+ && !primaryAuthRequired
&& !mKeyguardGoingAway
&& !mSecureCameraLaunched;
@@ -2604,9 +2627,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
shouldTriggerActiveUnlock,
awakeKeyguard,
mAuthInterruptActive,
- isEncryptedOrTimedOut,
- fpLockedout,
- isLockDown,
+ fpLockedOut,
+ primaryAuthRequired,
mSwitchingUser,
triggerActiveUnlockForAssistant,
userCanDismissLockScreen));
@@ -2658,7 +2680,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
&& !fingerprintDisabledForUser
&& (!mKeyguardGoingAway || !mDeviceInteractive)
&& mIsPrimaryUser
- && biometricEnabledForUser;
+ && biometricEnabledForUser
+ && !isUserInLockdown(user);
final boolean strongerAuthRequired = !isUnlockingWithFingerprintAllowed();
final boolean isSideFps = isSfpsSupported() && isSfpsEnrolled();
final boolean shouldListenBouncerState =
@@ -2720,14 +2743,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
final boolean awakeKeyguard = isKeyguardVisible() && mDeviceInteractive
&& !statusBarShadeLocked;
final int user = getCurrentUser();
- final int strongAuth = mStrongAuthTracker.getStrongAuthForUser(user);
- final boolean isLockDown =
- containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW)
- || containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
- final boolean isEncryptedOrTimedOut =
- containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_BOOT)
- || containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_TIMEOUT);
- final boolean fpLockedOut = isFingerprintLockedOut();
+ final boolean faceAuthAllowed = isUnlockingWithBiometricAllowed(FACE);
final boolean canBypass = mKeyguardBypassController != null
&& mKeyguardBypassController.canBypass();
// There's no reason to ask the HAL for authentication when the user can dismiss the
@@ -2735,20 +2751,15 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
// the lock screen even when TrustAgents are keeping the device unlocked.
final boolean userNotTrustedOrDetectionIsNeeded = !getUserHasTrust(user) || canBypass;
- // Scan even when encrypted or timeout to show a preemptive bouncer when bypassing.
- // Lock-down mode shouldn't scan, since it is more explicit.
- boolean strongAuthAllowsScanning = (!isEncryptedOrTimedOut || canBypass
- && !mPrimaryBouncerFullyShown);
-
- // If the device supports face detection (without authentication) and bypass is enabled,
- // allow face scanning to happen if the device is in lockdown mode.
- // Otherwise, prevent scanning.
- final boolean supportsDetectOnly = !mFaceSensorProperties.isEmpty()
- && canBypass
- && mFaceSensorProperties.get(0).supportsFaceDetection;
- if (isLockDown && !supportsDetectOnly) {
- strongAuthAllowsScanning = false;
- }
+ // If the device supports face detection (without authentication), if bypass is enabled,
+ // allow face detection to happen even if stronger auth is required. When face is detected,
+ // we show the bouncer. However, if the user manually locked down the device themselves,
+ // never attempt to detect face.
+ final boolean supportsDetect = !mFaceSensorProperties.isEmpty()
+ && mFaceSensorProperties.get(0).supportsFaceDetection
+ && canBypass && !mPrimaryBouncerIsOrWillBeShowing
+ && !isUserInLockdown(user);
+ final boolean faceAuthAllowedOrDetectionIsNeeded = faceAuthAllowed || supportsDetect;
// If the face or fp has recently been authenticated do not attempt to authenticate again.
final boolean faceAndFpNotAuthenticated = !getUserUnlockedWithBiometric(user);
@@ -2769,14 +2780,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
|| mUdfpsBouncerShowing)
&& !mSwitchingUser && !faceDisabledForUser && userNotTrustedOrDetectionIsNeeded
&& !mKeyguardGoingAway && biometricEnabledForUser
- && strongAuthAllowsScanning && mIsPrimaryUser
+ && faceAuthAllowedOrDetectionIsNeeded && mIsPrimaryUser
&& (!mSecureCameraLaunched || mOccludingAppRequestingFace)
&& faceAndFpNotAuthenticated
- && !mGoingToSleep
- // We only care about fp locked out state and not face because we still trigger
- // face auth even when face is locked out to show the user a message that face
- // unlock was supposed to run but didn't
- && !fpLockedOut;
+ && !mGoingToSleep;
// Aggregate relevant fields for debug logging.
maybeLogListenerModelData(
@@ -2786,19 +2793,19 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
shouldListen,
mAuthInterruptActive,
biometricEnabledForUser,
- mPrimaryBouncerFullyShown,
+ mPrimaryBouncerFullyShown,
faceAndFpNotAuthenticated,
+ faceAuthAllowed,
faceDisabledForUser,
isFaceLockedOut(),
- fpLockedOut,
mGoingToSleep,
awakeKeyguard,
mKeyguardGoingAway,
shouldListenForFaceAssistant,
mOccludingAppRequestingFace,
mIsPrimaryUser,
- strongAuthAllowsScanning,
mSecureCameraLaunched,
+ supportsDetect,
mSwitchingUser,
mUdfpsBouncerShowing,
isUdfpsFingerDown,
@@ -2902,9 +2909,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
// This would need to be updated for multi-sensor devices
final boolean supportsFaceDetection = !mFaceSensorProperties.isEmpty()
&& mFaceSensorProperties.get(0).supportsFaceDetection;
- if (isEncryptedOrLockdown(userId) && supportsFaceDetection) {
+ if (!isUnlockingWithBiometricAllowed(FACE) && supportsFaceDetection) {
+ mLogger.v("startListeningForFace - detect");
mFaceManager.detectFace(mFaceCancelSignal, mFaceDetectionCallback, userId);
} else {
+ mLogger.v("startListeningForFace - authenticate");
final boolean isBypassEnabled = mKeyguardBypassController != null
&& mKeyguardBypassController.isBypassEnabled();
mFaceManager.authenticate(null /* crypto */, mFaceCancelSignal,
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
index 1f6441a47092..ceebe4c69091 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
@@ -225,12 +225,13 @@ class KeyguardUpdateMonitorLogger @Inject constructor(
{ "Retrying face after HW unavailable, attempt $int1" })
}
- fun logRetryAfterFpError(msgId: Int, errString: String?) {
+ fun logRetryAfterFpErrorWithDelay(msgId: Int, errString: String?, delay: Int) {
logBuffer.log(TAG, DEBUG, {
int1 = msgId
+ int2 = delay
str1 = "$errString"
}, {
- "Fingerprint retrying auth due to($int1) -> $str1"
+ "Fingerprint scheduling retry auth after $int2 ms due to($int1) -> $str1"
})
}
diff --git a/packages/SystemUI/src/com/android/systemui/CoreStartable.java b/packages/SystemUI/src/com/android/systemui/CoreStartable.java
index 929ebea37eef..becf5b39e9df 100644
--- a/packages/SystemUI/src/com/android/systemui/CoreStartable.java
+++ b/packages/SystemUI/src/com/android/systemui/CoreStartable.java
@@ -39,10 +39,13 @@ import java.io.PrintWriter;
*/
public interface CoreStartable extends Dumpable {
- /** Main entry point for implementations. Called shortly after app startup. */
+ /** Main entry point for implementations. Called shortly after SysUI startup. */
void start();
- /** */
+ /** Called when the device configuration changes. This will not be called before
+ * {@link #start()}, but it could be called before {@link #onBootCompleted()}.
+ *
+ * @see android.app.Application#onConfigurationChanged(Configuration) */
default void onConfigurationChanged(Configuration newConfig) {
}
@@ -50,7 +53,11 @@ public interface CoreStartable extends Dumpable {
default void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
}
- /** Called when the device reports BOOT_COMPLETED. */
+ /** Called immediately after the system broadcasts
+ * {@link android.content.Intent#ACTION_LOCKED_BOOT_COMPLETED} or during SysUI startup if the
+ * property {@code sys.boot_completed} is already set to 1. The latter typically occurs when
+ * starting a new SysUI instance, such as when starting SysUI for a secondary user.
+ * {@link #onBootCompleted()} will never be called before {@link #start()}. */
default void onBootCompleted() {
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index 02a6d7be7143..e6f559b3da5f 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -210,8 +210,10 @@ public class ScreenDecorations implements CoreStartable, Tunable , Dumpable {
(FaceScanningOverlay) getOverlayView(mFaceScanningViewId);
if (faceScanningOverlay != null) {
faceScanningOverlay.setHideOverlayRunnable(() -> {
+ Trace.beginSection("ScreenDecorations#hideOverlayRunnable");
updateOverlayWindowVisibilityIfViewExists(
faceScanningOverlay.findViewById(mFaceScanningViewId));
+ Trace.endSection();
});
faceScanningOverlay.enableShowProtection(false);
}
@@ -273,16 +275,18 @@ public class ScreenDecorations implements CoreStartable, Tunable , Dumpable {
if (mOverlays == null || !shouldOptimizeVisibility()) {
return;
}
-
+ Trace.beginSection("ScreenDecorations#updateOverlayWindowVisibilityIfViewExists");
for (final OverlayWindow overlay : mOverlays) {
if (overlay == null) {
continue;
}
if (overlay.getView(view.getId()) != null) {
overlay.getRootView().setVisibility(getWindowVisibility(overlay, true));
+ Trace.endSection();
return;
}
}
+ Trace.endSection();
});
}
@@ -370,6 +374,7 @@ public class ScreenDecorations implements CoreStartable, Tunable , Dumpable {
}
private void startOnScreenDecorationsThread() {
+ Trace.beginSection("ScreenDecorations#startOnScreenDecorationsThread");
mWindowManager = mContext.getSystemService(WindowManager.class);
mDisplayManager = mContext.getSystemService(DisplayManager.class);
mContext.getDisplay().getDisplayInfo(mDisplayInfo);
@@ -472,6 +477,7 @@ public class ScreenDecorations implements CoreStartable, Tunable , Dumpable {
mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);
updateConfiguration();
+ Trace.endSection();
}
@VisibleForTesting
@@ -521,6 +527,12 @@ public class ScreenDecorations implements CoreStartable, Tunable , Dumpable {
}
private void setupDecorations() {
+ Trace.beginSection("ScreenDecorations#setupDecorations");
+ setupDecorationsInner();
+ Trace.endSection();
+ }
+
+ private void setupDecorationsInner() {
if (hasRoundedCorners() || shouldDrawCutout() || isPrivacyDotEnabled()
|| mFaceScanningFactory.getHasProviders()) {
@@ -573,7 +585,11 @@ public class ScreenDecorations implements CoreStartable, Tunable , Dumpable {
return;
}
- mMainExecutor.execute(() -> mTunerService.addTunable(this, SIZE));
+ mMainExecutor.execute(() -> {
+ Trace.beginSection("ScreenDecorations#addTunable");
+ mTunerService.addTunable(this, SIZE);
+ Trace.endSection();
+ });
// Watch color inversion and invert the overlay as needed.
if (mColorInversionSetting == null) {
@@ -593,7 +609,11 @@ public class ScreenDecorations implements CoreStartable, Tunable , Dumpable {
mUserTracker.addCallback(mUserChangedCallback, mExecutor);
mIsRegistered = true;
} else {
- mMainExecutor.execute(() -> mTunerService.removeTunable(this));
+ mMainExecutor.execute(() -> {
+ Trace.beginSection("ScreenDecorations#removeTunable");
+ mTunerService.removeTunable(this);
+ Trace.endSection();
+ });
if (mColorInversionSetting != null) {
mColorInversionSetting.setListening(false);
@@ -939,6 +959,7 @@ public class ScreenDecorations implements CoreStartable, Tunable , Dumpable {
}
mExecutor.execute(() -> {
+ Trace.beginSection("ScreenDecorations#onConfigurationChanged");
int oldRotation = mRotation;
mPendingConfigChange = false;
updateConfiguration();
@@ -951,6 +972,7 @@ public class ScreenDecorations implements CoreStartable, Tunable , Dumpable {
// the updated rotation).
updateLayoutParams();
}
+ Trace.endSection();
});
}
@@ -1119,6 +1141,7 @@ public class ScreenDecorations implements CoreStartable, Tunable , Dumpable {
if (mOverlays == null || !SIZE.equals(key)) {
return;
}
+ Trace.beginSection("ScreenDecorations#onTuningChanged");
try {
final int sizeFactor = Integer.parseInt(newValue);
mRoundedCornerResDelegate.setTuningSizeFactor(sizeFactor);
@@ -1132,6 +1155,7 @@ public class ScreenDecorations implements CoreStartable, Tunable , Dumpable {
R.id.rounded_corner_bottom_right
});
updateHwLayerRoundedCornerExistAndSize();
+ Trace.endSection();
});
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index a7519cf713d5..db2239b7e680 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -623,6 +623,10 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
getFingerprintSensorLocationInNaturalOrientation(),
mCachedDisplayInfo);
}
+
+ for (final Callback cb : mCallbacks) {
+ cb.onFingerprintLocationChanged();
+ }
}
/**
@@ -644,6 +648,10 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
mCachedDisplayInfo
);
}
+
+ for (final Callback cb : mCallbacks) {
+ cb.onFaceSensorLocationChanged();
+ }
}
/**
@@ -1325,8 +1333,24 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
default void onBiometricPromptDismissed() {}
/**
- * The location in pixels can change due to resolution changes.
+ * Called when the location of the fingerprint sensor changes. The location in pixels can
+ * change due to resolution changes.
+ */
+ default void onFingerprintLocationChanged() {}
+
+ /**
+ * Called when the location of the under display fingerprint sensor changes. The location in
+ * pixels can change due to resolution changes.
+ *
+ * On devices with UDFPS, this is always called alongside
+ * {@link #onFingerprintLocationChanged}.
*/
default void onUdfpsLocationChanged() {}
+
+ /**
+ * Called when the location of the face unlock sensor (typically the front facing camera)
+ * changes. The location in pixels can change due to resolution changes.
+ */
+ default void onFaceSensorLocationChanged() {}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
index 6ac54feeb935..d561cd7af7f0 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
@@ -29,6 +29,8 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.settingslib.Utils
import com.android.systemui.R
import com.android.systemui.animation.Interpolators
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.CircleReveal
@@ -71,7 +73,8 @@ class AuthRippleController @Inject constructor(
private val biometricUnlockController: BiometricUnlockController,
private val udfpsControllerProvider: Provider<UdfpsController>,
private val statusBarStateController: StatusBarStateController,
- rippleView: AuthRippleView?
+ private val featureFlags: FeatureFlags,
+ rippleView: AuthRippleView?
) : ViewController<AuthRippleView>(rippleView), KeyguardStateController.Callback,
WakefulnessLifecycle.Observer {
@@ -159,12 +162,17 @@ class AuthRippleController @Inject constructor(
private fun showUnlockedRipple() {
notificationShadeWindowController.setForcePluginOpen(true, this)
- val lightRevealScrim = centralSurfaces.lightRevealScrim
- if (statusBarStateController.isDozing || biometricUnlockController.isWakeAndUnlock) {
- circleReveal?.let {
- lightRevealScrim?.revealAmount = 0f
- lightRevealScrim?.revealEffect = it
- startLightRevealScrimOnKeyguardFadingAway = true
+
+ // This code path is not used if the KeyguardTransitionRepository is managing the light
+ // reveal scrim.
+ if (!featureFlags.isEnabled(Flags.LIGHT_REVEAL_MIGRATION)) {
+ val lightRevealScrim = centralSurfaces.lightRevealScrim
+ if (statusBarStateController.isDozing || biometricUnlockController.isWakeAndUnlock) {
+ circleReveal?.let {
+ lightRevealScrim?.revealAmount = 0f
+ lightRevealScrim?.revealEffect = it
+ startLightRevealScrimOnKeyguardFadingAway = true
+ }
}
}
@@ -177,6 +185,10 @@ class AuthRippleController @Inject constructor(
}
override fun onKeyguardFadingAwayChanged() {
+ if (featureFlags.isEnabled(Flags.LIGHT_REVEAL_MIGRATION)) {
+ return
+ }
+
if (keyguardStateController.isKeyguardFadingAway) {
val lightRevealScrim = centralSurfaces.lightRevealScrim
if (startLightRevealScrimOnKeyguardFadingAway && lightRevealScrim != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
index ed214302fdec..17ebdadfa93f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
@@ -370,11 +370,15 @@ private fun WindowInsets.hasBigNavigationBar(): Boolean =
private fun LottieAnimationView.addOverlayDynamicColor(context: Context) {
fun update() {
val c = context.getColor(R.color.biometric_dialog_accent)
+ val chevronFill = context.getColor(R.color.sfps_chevron_fill)
for (key in listOf(".blue600", ".blue400")) {
addValueCallback(KeyPath(key, "**"), LottieProperty.COLOR_FILTER) {
PorterDuffColorFilter(c, PorterDuff.Mode.SRC_ATOP)
}
}
+ addValueCallback(KeyPath(".black", "**"), LottieProperty.COLOR_FILTER) {
+ PorterDuffColorFilter(chevronFill, PorterDuff.Mode.SRC_ATOP)
+ }
}
if (composition != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 19b054879d89..7fd4d6ac1b34 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -97,6 +97,7 @@ import java.util.Set;
import java.util.concurrent.Executor;
import javax.inject.Inject;
+import javax.inject.Provider;
import kotlin.Unit;
@@ -747,7 +748,7 @@ public class UdfpsController implements DozeReceiver, Dumpable {
@NonNull SystemUIDialogManager dialogManager,
@NonNull LatencyTracker latencyTracker,
@NonNull ActivityLaunchAnimator activityLaunchAnimator,
- @NonNull Optional<AlternateUdfpsTouchProvider> alternateTouchProvider,
+ @NonNull Optional<Provider<AlternateUdfpsTouchProvider>> alternateTouchProvider,
@NonNull @BiometricsBackground Executor biometricsExecutor,
@NonNull PrimaryBouncerInteractor primaryBouncerInteractor,
@NonNull SinglePointerTouchProcessor singlePointerTouchProcessor) {
@@ -779,7 +780,7 @@ public class UdfpsController implements DozeReceiver, Dumpable {
mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
mLatencyTracker = latencyTracker;
mActivityLaunchAnimator = activityLaunchAnimator;
- mAlternateTouchProvider = alternateTouchProvider.orElse(null);
+ mAlternateTouchProvider = alternateTouchProvider.map(Provider::get).orElse(null);
mSensorProps = new FingerprintSensorPropertiesInternal(
-1 /* sensorId */,
SensorProperties.STRENGTH_CONVENIENCE,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlay.kt
index 142642a2411f..802b9b6c0295 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlay.kt
@@ -42,6 +42,7 @@ import com.android.systemui.util.concurrency.Execution
import java.util.Optional
import java.util.concurrent.Executor
import javax.inject.Inject
+import javax.inject.Provider
import kotlin.math.cos
import kotlin.math.pow
import kotlin.math.sin
@@ -64,7 +65,7 @@ constructor(
private val fingerprintManager: FingerprintManager?,
private val handler: Handler,
private val biometricExecutor: Executor,
- private val alternateTouchProvider: Optional<AlternateUdfpsTouchProvider>,
+ private val alternateTouchProvider: Optional<Provider<AlternateUdfpsTouchProvider>>,
@Main private val fgExecutor: DelayableExecutor,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
private val authController: AuthController,
@@ -126,6 +127,7 @@ constructor(
if (!processedMotionEvent && goodOverlap) {
biometricExecutor.execute {
alternateTouchProvider
+ .map(Provider<AlternateUdfpsTouchProvider>::get)
.get()
.onPointerDown(
requestId,
@@ -142,7 +144,10 @@ constructor(
view.configureDisplay {
biometricExecutor.execute {
- alternateTouchProvider.get().onUiReady()
+ alternateTouchProvider
+ .map(Provider<AlternateUdfpsTouchProvider>::get)
+ .get()
+ .onUiReady()
}
}
@@ -158,7 +163,10 @@ constructor(
MotionEvent.ACTION_CANCEL -> {
if (processedMotionEvent && alternateTouchProvider.isPresent) {
biometricExecutor.execute {
- alternateTouchProvider.get().onPointerUp(requestId)
+ alternateTouchProvider
+ .map(Provider<AlternateUdfpsTouchProvider>::get)
+ .get()
+ .onPointerUp(requestId)
}
fgExecutor.execute {
if (keyguardUpdateMonitor.isFingerprintDetectionRunning) {
@@ -241,7 +249,10 @@ constructor(
if (overlayView != null && isShowing && alternateTouchProvider.isPresent) {
if (processedMotionEvent) {
biometricExecutor.execute {
- alternateTouchProvider.get().onPointerUp(requestId)
+ alternateTouchProvider
+ .map(Provider<AlternateUdfpsTouchProvider>::get)
+ .get()
+ .onPointerUp(requestId)
}
fgExecutor.execute {
if (keyguardUpdateMonitor.isFingerprintDetectionRunning) {
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
index fb37defd844d..63c20651fcd5 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
@@ -301,7 +301,9 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv
} else {
mView.showDefaultTextPreview();
}
- maybeShowRemoteCopy(clipData);
+ if (!isRemote) {
+ maybeShowRemoteCopy(clipData);
+ }
animateIn();
mView.announceForAccessibility(accessibilityAnnouncement);
if (isRemote) {
diff --git a/packages/SystemUI/src/com/android/systemui/common/shared/model/TintedIcon.kt b/packages/SystemUI/src/com/android/systemui/common/shared/model/TintedIcon.kt
new file mode 100644
index 000000000000..5dabbbb81701
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/common/shared/model/TintedIcon.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.common.shared.model
+
+import androidx.annotation.AttrRes
+
+/** Models an icon with a specific tint. */
+data class TintedIcon(
+ val icon: Icon,
+ @AttrRes val tintAttr: Int?,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/binder/TintedIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/common/ui/binder/TintedIconViewBinder.kt
new file mode 100644
index 000000000000..dea8cfda80c3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/binder/TintedIconViewBinder.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.common.ui.binder
+
+import android.widget.ImageView
+import com.android.settingslib.Utils
+import com.android.systemui.common.shared.model.TintedIcon
+
+object TintedIconViewBinder {
+ /**
+ * Binds the given tinted icon to the view.
+ *
+ * [TintedIcon.tintAttr] will always be applied, meaning that if it is null, then the tint
+ * *will* be reset to null.
+ */
+ fun bind(
+ tintedIcon: TintedIcon,
+ view: ImageView,
+ ) {
+ IconViewBinder.bind(tintedIcon.icon, view)
+ view.imageTintList =
+ if (tintedIcon.tintAttr != null) {
+ Utils.getColorAttr(view.context, tintedIcon.tintAttr)
+ } else {
+ null
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt
index 77d0496e43db..27466d4f58bc 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt
@@ -19,7 +19,7 @@ package com.android.systemui.controls.dagger
import android.content.Context
import com.android.internal.widget.LockPatternUtils
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
-import com.android.systemui.controls.ControlsSettingsRepository
+import com.android.systemui.controls.settings.ControlsSettingsRepository
import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.controls.controller.ControlsTileResourceConfiguration
import com.android.systemui.controls.controller.ControlsTileResourceConfigurationImpl
diff --git a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt
index 9ae605e30a83..6d6410de1a91 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt
@@ -20,8 +20,8 @@ import android.app.Activity
import android.content.pm.PackageManager
import com.android.systemui.controls.ControlsMetricsLogger
import com.android.systemui.controls.ControlsMetricsLoggerImpl
-import com.android.systemui.controls.ControlsSettingsRepository
-import com.android.systemui.controls.ControlsSettingsRepositoryImpl
+import com.android.systemui.controls.settings.ControlsSettingsRepository
+import com.android.systemui.controls.settings.ControlsSettingsRepositoryImpl
import com.android.systemui.controls.controller.ControlsBindingController
import com.android.systemui.controls.controller.ControlsBindingControllerImpl
import com.android.systemui.controls.controller.ControlsController
@@ -34,6 +34,8 @@ import com.android.systemui.controls.management.ControlsListingController
import com.android.systemui.controls.management.ControlsListingControllerImpl
import com.android.systemui.controls.management.ControlsProviderSelectorActivity
import com.android.systemui.controls.management.ControlsRequestDialog
+import com.android.systemui.controls.settings.ControlsSettingsDialogManager
+import com.android.systemui.controls.settings.ControlsSettingsDialogManagerImpl
import com.android.systemui.controls.ui.ControlActionCoordinator
import com.android.systemui.controls.ui.ControlActionCoordinatorImpl
import com.android.systemui.controls.ui.ControlsActivity
@@ -90,6 +92,11 @@ abstract class ControlsModule {
): ControlsSettingsRepository
@Binds
+ abstract fun provideDialogManager(
+ manager: ControlsSettingsDialogManagerImpl
+ ): ControlsSettingsDialogManager
+
+ @Binds
abstract fun provideMetricsLogger(logger: ControlsMetricsLoggerImpl): ControlsMetricsLogger
@Binds
diff --git a/packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsDialogManager.kt b/packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsDialogManager.kt
new file mode 100644
index 000000000000..bb2e2d701aa0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsDialogManager.kt
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.controls.settings
+
+import android.app.AlertDialog
+import android.content.Context
+import android.content.Context.MODE_PRIVATE
+import android.content.DialogInterface
+import android.content.SharedPreferences
+import android.provider.Settings
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.R
+import com.android.systemui.controls.settings.ControlsSettingsDialogManager.Companion.MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG
+import com.android.systemui.controls.settings.ControlsSettingsDialogManager.Companion.PREFS_SETTINGS_DIALOG_ATTEMPTS
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.settings.UserFileManager
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
+import com.android.systemui.util.settings.SecureSettings
+import javax.inject.Inject
+
+/**
+ * Manager to display a dialog to prompt user to enable controls related Settings:
+ *
+ * * [Settings.Secure.LOCKSCREEN_SHOW_CONTROLS]
+ * * [Settings.Secure.LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS]
+ */
+interface ControlsSettingsDialogManager {
+
+ /**
+ * Shows the corresponding dialog. In order for a dialog to appear, the following must be true
+ *
+ * * At least one of the Settings in [ControlsSettingsRepository] are `false`.
+ * * The dialog has not been seen by the user too many times (as defined by
+ * [MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG]).
+ *
+ * When the dialogs are shown, the following outcomes are possible:
+ * * User cancels the dialog by clicking outside or going back: we register that the dialog was
+ * seen but the settings don't change.
+ * * User responds negatively to the dialog: we register that the user doesn't want to change
+ * the settings (dialog will not appear again) and the settings don't change.
+ * * User responds positively to the dialog: the settings are set to `true` and the dialog will
+ * not appear again.
+ * * SystemUI closes the dialogs (for example, the activity showing it is closed). In this case,
+ * we don't modify anything.
+ *
+ * Of those four scenarios, only the first three will cause [onAttemptCompleted] to be called.
+ * It will also be called if the dialogs are not shown.
+ */
+ fun maybeShowDialog(activityContext: Context, onAttemptCompleted: () -> Unit)
+
+ /**
+ * Closes the dialog without registering anything from the user. The state of the settings after
+ * this is called will be the same as before the dialogs were shown.
+ */
+ fun closeDialog()
+
+ companion object {
+ @VisibleForTesting internal const val MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG = 2
+ @VisibleForTesting
+ internal const val PREFS_SETTINGS_DIALOG_ATTEMPTS = "show_settings_attempts"
+ }
+}
+
+@SysUISingleton
+class ControlsSettingsDialogManagerImpl
+@VisibleForTesting
+internal constructor(
+ private val secureSettings: SecureSettings,
+ private val userFileManager: UserFileManager,
+ private val controlsSettingsRepository: ControlsSettingsRepository,
+ private val userTracker: UserTracker,
+ private val activityStarter: ActivityStarter,
+ private val dialogProvider: (context: Context, theme: Int) -> AlertDialog
+) : ControlsSettingsDialogManager {
+
+ @Inject
+ constructor(
+ secureSettings: SecureSettings,
+ userFileManager: UserFileManager,
+ controlsSettingsRepository: ControlsSettingsRepository,
+ userTracker: UserTracker,
+ activityStarter: ActivityStarter
+ ) : this(
+ secureSettings,
+ userFileManager,
+ controlsSettingsRepository,
+ userTracker,
+ activityStarter,
+ { context, theme -> SettingsDialog(context, theme) }
+ )
+
+ private var dialog: AlertDialog? = null
+ private set
+
+ private val showDeviceControlsInLockscreen: Boolean
+ get() = controlsSettingsRepository.canShowControlsInLockscreen.value
+
+ private val allowTrivialControls: Boolean
+ get() = controlsSettingsRepository.allowActionOnTrivialControlsInLockscreen.value
+
+ override fun maybeShowDialog(activityContext: Context, onAttemptCompleted: () -> Unit) {
+ closeDialog()
+
+ val prefs =
+ userFileManager.getSharedPreferences(
+ DeviceControlsControllerImpl.PREFS_CONTROLS_FILE,
+ MODE_PRIVATE,
+ userTracker.userId
+ )
+ val attempts = prefs.getInt(PREFS_SETTINGS_DIALOG_ATTEMPTS, 0)
+ if (
+ attempts >= MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG ||
+ (showDeviceControlsInLockscreen && allowTrivialControls)
+ ) {
+ onAttemptCompleted()
+ return
+ }
+
+ val listener = DialogListener(prefs, attempts, onAttemptCompleted)
+ val d =
+ dialogProvider(activityContext, R.style.Theme_SystemUI_Dialog).apply {
+ setIcon(R.drawable.ic_warning)
+ setOnCancelListener(listener)
+ setNeutralButton(R.string.controls_settings_dialog_neutral_button, listener)
+ setPositiveButton(R.string.controls_settings_dialog_positive_button, listener)
+ if (showDeviceControlsInLockscreen) {
+ setTitle(R.string.controls_settings_trivial_controls_dialog_title)
+ setMessage(R.string.controls_settings_trivial_controls_dialog_message)
+ } else {
+ setTitle(R.string.controls_settings_show_controls_dialog_title)
+ setMessage(R.string.controls_settings_show_controls_dialog_message)
+ }
+ }
+
+ SystemUIDialog.registerDismissListener(d) { dialog = null }
+ SystemUIDialog.setDialogSize(d)
+ SystemUIDialog.setShowForAllUsers(d, true)
+ dialog = d
+ d.show()
+ }
+
+ private fun turnOnSettingSecurely(settings: List<String>) {
+ val action =
+ ActivityStarter.OnDismissAction {
+ settings.forEach { setting ->
+ secureSettings.putIntForUser(setting, 1, userTracker.userId)
+ }
+ true
+ }
+ activityStarter.dismissKeyguardThenExecute(
+ action,
+ /* cancel */ null,
+ /* afterKeyguardGone */ true
+ )
+ }
+
+ override fun closeDialog() {
+ dialog?.dismiss()
+ }
+
+ private inner class DialogListener(
+ private val prefs: SharedPreferences,
+ private val attempts: Int,
+ private val onComplete: () -> Unit
+ ) : DialogInterface.OnClickListener, DialogInterface.OnCancelListener {
+ override fun onClick(dialog: DialogInterface?, which: Int) {
+ if (dialog == null) return
+ if (which == DialogInterface.BUTTON_POSITIVE) {
+ val settings = mutableListOf(Settings.Secure.LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS)
+ if (!showDeviceControlsInLockscreen) {
+ settings.add(Settings.Secure.LOCKSCREEN_SHOW_CONTROLS)
+ }
+ turnOnSettingSecurely(settings)
+ }
+ if (attempts != MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG) {
+ prefs
+ .edit()
+ .putInt(PREFS_SETTINGS_DIALOG_ATTEMPTS, MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG)
+ .apply()
+ }
+ onComplete()
+ }
+
+ override fun onCancel(dialog: DialogInterface?) {
+ if (dialog == null) return
+ if (attempts < MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG) {
+ prefs.edit().putInt(PREFS_SETTINGS_DIALOG_ATTEMPTS, attempts + 1).apply()
+ }
+ onComplete()
+ }
+ }
+
+ private fun AlertDialog.setNeutralButton(
+ msgId: Int,
+ listener: DialogInterface.OnClickListener
+ ) {
+ setButton(DialogInterface.BUTTON_NEUTRAL, context.getText(msgId), listener)
+ }
+
+ private fun AlertDialog.setPositiveButton(
+ msgId: Int,
+ listener: DialogInterface.OnClickListener
+ ) {
+ setButton(DialogInterface.BUTTON_POSITIVE, context.getText(msgId), listener)
+ }
+
+ private fun AlertDialog.setMessage(msgId: Int) {
+ setMessage(context.getText(msgId))
+ }
+
+ /** This is necessary because the constructors are `protected`. */
+ private class SettingsDialog(context: Context, theme: Int) : AlertDialog(context, theme)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ControlsSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsRepository.kt
index 3d10ab906f2f..df2831c6eb96 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ControlsSettingsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsRepository.kt
@@ -15,7 +15,7 @@
*
*/
-package com.android.systemui.controls
+package com.android.systemui.controls.settings
import kotlinx.coroutines.flow.StateFlow
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ControlsSettingsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsRepositoryImpl.kt
index 9dc422a09674..8e3b5109339c 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ControlsSettingsRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsRepositoryImpl.kt
@@ -15,7 +15,7 @@
*
*/
-package com.android.systemui.controls
+package com.android.systemui.controls.settings
import android.provider.Settings
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
index 041ed1d557d7..99a10a33ab0f 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
@@ -19,15 +19,12 @@ package com.android.systemui.controls.ui
import android.annotation.AnyThread
import android.annotation.MainThread
import android.app.Activity
-import android.app.AlertDialog
import android.app.Dialog
import android.app.PendingIntent
import android.content.Context
import android.content.pm.PackageManager
import android.content.pm.ResolveInfo
-import android.os.UserHandle
import android.os.VibrationEffect
-import android.provider.Settings.Secure
import android.service.controls.Control
import android.service.controls.actions.BooleanAction
import android.service.controls.actions.CommandAction
@@ -35,39 +32,36 @@ import android.service.controls.actions.FloatAction
import android.util.Log
import android.view.HapticFeedbackConstants
import com.android.internal.annotations.VisibleForTesting
-import com.android.systemui.R
import com.android.systemui.broadcast.BroadcastSender
import com.android.systemui.controls.ControlsMetricsLogger
-import com.android.systemui.controls.ControlsSettingsRepository
+import com.android.systemui.controls.settings.ControlsSettingsDialogManager
+import com.android.systemui.controls.settings.ControlsSettingsRepository
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.settings.UserContextProvider
import com.android.systemui.statusbar.VibratorHelper
-import com.android.systemui.statusbar.phone.SystemUIDialog
-import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl.Companion.PREFS_CONTROLS_FILE
-import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl.Companion.PREFS_SETTINGS_DIALOG_ATTEMPTS
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.concurrency.DelayableExecutor
-import com.android.systemui.util.settings.SecureSettings
import com.android.wm.shell.TaskViewFactory
import java.util.Optional
import javax.inject.Inject
@SysUISingleton
class ControlActionCoordinatorImpl @Inject constructor(
- private val context: Context,
- private val bgExecutor: DelayableExecutor,
- @Main private val uiExecutor: DelayableExecutor,
- private val activityStarter: ActivityStarter,
- private val broadcastSender: BroadcastSender,
- private val keyguardStateController: KeyguardStateController,
- private val taskViewFactory: Optional<TaskViewFactory>,
- private val controlsMetricsLogger: ControlsMetricsLogger,
- private val vibrator: VibratorHelper,
- private val secureSettings: SecureSettings,
- private val userContextProvider: UserContextProvider,
- private val controlsSettingsRepository: ControlsSettingsRepository,
+ private val context: Context,
+ private val bgExecutor: DelayableExecutor,
+ @Main private val uiExecutor: DelayableExecutor,
+ private val activityStarter: ActivityStarter,
+ private val broadcastSender: BroadcastSender,
+ private val keyguardStateController: KeyguardStateController,
+ private val taskViewFactory: Optional<TaskViewFactory>,
+ private val controlsMetricsLogger: ControlsMetricsLogger,
+ private val vibrator: VibratorHelper,
+ private val controlsSettingsRepository: ControlsSettingsRepository,
+ private val controlsSettingsDialogManager: ControlsSettingsDialogManager,
+ private val featureFlags: FeatureFlags,
) : ControlActionCoordinator {
private var dialog: Dialog? = null
private var pendingAction: Action? = null
@@ -76,16 +70,16 @@ class ControlActionCoordinatorImpl @Inject constructor(
get() = !keyguardStateController.isUnlocked()
private val allowTrivialControls: Boolean
get() = controlsSettingsRepository.allowActionOnTrivialControlsInLockscreen.value
- private val showDeviceControlsInLockscreen: Boolean
- get() = controlsSettingsRepository.canShowControlsInLockscreen.value
override lateinit var activityContext: Context
companion object {
private const val RESPONSE_TIMEOUT_IN_MILLIS = 3000L
- private const val MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG = 2
}
override fun closeDialogs() {
+ if (!featureFlags.isEnabled(Flags.USE_APP_PANELS)) {
+ controlsSettingsDialogManager.closeDialog()
+ }
val isActivityFinishing =
(activityContext as? Activity)?.let { it.isFinishing || it.isDestroyed }
if (isActivityFinishing == true) {
@@ -253,71 +247,9 @@ class ControlActionCoordinatorImpl @Inject constructor(
if (action.authIsRequired) {
return
}
- val prefs = userContextProvider.userContext.getSharedPreferences(
- PREFS_CONTROLS_FILE, Context.MODE_PRIVATE)
- val attempts = prefs.getInt(PREFS_SETTINGS_DIALOG_ATTEMPTS, 0)
- if (attempts >= MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG ||
- (showDeviceControlsInLockscreen && allowTrivialControls)) {
- return
+ if (!featureFlags.isEnabled(Flags.USE_APP_PANELS)) {
+ controlsSettingsDialogManager.maybeShowDialog(activityContext) {}
}
- val builder = AlertDialog
- .Builder(activityContext, R.style.Theme_SystemUI_Dialog)
- .setIcon(R.drawable.ic_warning)
- .setOnCancelListener {
- if (attempts < MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG) {
- prefs.edit().putInt(PREFS_SETTINGS_DIALOG_ATTEMPTS, attempts + 1)
- .commit()
- }
- true
- }
- .setNeutralButton(R.string.controls_settings_dialog_neutral_button) { _, _ ->
- if (attempts != MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG) {
- prefs.edit().putInt(PREFS_SETTINGS_DIALOG_ATTEMPTS,
- MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG)
- .commit()
- }
- true
- }
-
- if (showDeviceControlsInLockscreen) {
- dialog = builder
- .setTitle(R.string.controls_settings_trivial_controls_dialog_title)
- .setMessage(R.string.controls_settings_trivial_controls_dialog_message)
- .setPositiveButton(R.string.controls_settings_dialog_positive_button) { _, _ ->
- if (attempts != MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG) {
- prefs.edit().putInt(PREFS_SETTINGS_DIALOG_ATTEMPTS,
- MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG)
- .commit()
- }
- secureSettings.putIntForUser(Secure.LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS, 1,
- UserHandle.USER_CURRENT)
- true
- }
- .create()
- } else {
- dialog = builder
- .setTitle(R.string.controls_settings_show_controls_dialog_title)
- .setMessage(R.string.controls_settings_show_controls_dialog_message)
- .setPositiveButton(R.string.controls_settings_dialog_positive_button) { _, _ ->
- if (attempts != MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG) {
- prefs.edit().putInt(PREFS_SETTINGS_DIALOG_ATTEMPTS,
- MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG)
- .commit()
- }
- secureSettings.putIntForUser(Secure.LOCKSCREEN_SHOW_CONTROLS,
- 1, UserHandle.USER_CURRENT)
- secureSettings.putIntForUser(Secure.LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS,
- 1, UserHandle.USER_CURRENT)
- true
- }
- .create()
- }
-
- SystemUIDialog.registerDismissListener(dialog)
- SystemUIDialog.setDialogSize(dialog)
-
- dialog?.create()
- dialog?.show()
}
@VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
index bd704c1ff086..5d611c4c8212 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
@@ -32,8 +32,10 @@ import androidx.activity.ComponentActivity
import com.android.systemui.R
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.controls.management.ControlsAnimations
+import com.android.systemui.controls.settings.ControlsSettingsDialogManager
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
+import com.android.systemui.statusbar.policy.KeyguardStateController
import javax.inject.Inject
/**
@@ -47,7 +49,9 @@ class ControlsActivity @Inject constructor(
private val uiController: ControlsUiController,
private val broadcastDispatcher: BroadcastDispatcher,
private val dreamManager: IDreamManager,
- private val featureFlags: FeatureFlags
+ private val featureFlags: FeatureFlags,
+ private val controlsSettingsDialogManager: ControlsSettingsDialogManager,
+ private val keyguardStateController: KeyguardStateController
) : ComponentActivity() {
private lateinit var parent: ViewGroup
@@ -92,7 +96,13 @@ class ControlsActivity @Inject constructor(
parent = requireViewById<ViewGroup>(R.id.global_actions_controls)
parent.alpha = 0f
- uiController.show(parent, { finishOrReturnToDream() }, this)
+ if (featureFlags.isEnabled(Flags.USE_APP_PANELS) && !keyguardStateController.isUnlocked) {
+ controlsSettingsDialogManager.maybeShowDialog(this) {
+ uiController.show(parent, { finishOrReturnToDream() }, this)
+ }
+ } else {
+ uiController.show(parent, { finishOrReturnToDream() }, this)
+ }
ControlsAnimations.enterAnimation(parent).start()
}
@@ -124,6 +134,7 @@ class ControlsActivity @Inject constructor(
mExitToDream = false
uiController.hide()
+ controlsSettingsDialogManager.closeDialog()
}
override fun onDestroy() {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
index 4c8e1ac968f9..fb678aa420bf 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -28,6 +28,7 @@ import android.content.Intent
import android.graphics.drawable.Drawable
import android.graphics.drawable.LayerDrawable
import android.service.controls.Control
+import android.service.controls.ControlsProviderService
import android.util.Log
import android.view.ContextThemeWrapper
import android.view.LayoutInflater
@@ -48,6 +49,7 @@ import com.android.systemui.Dumpable
import com.android.systemui.R
import com.android.systemui.controls.ControlsMetricsLogger
import com.android.systemui.controls.ControlsServiceInfo
+import com.android.systemui.controls.settings.ControlsSettingsRepository
import com.android.systemui.controls.CustomIconCache
import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.controls.controller.StructureInfo
@@ -96,6 +98,7 @@ class ControlsUiControllerImpl @Inject constructor (
private val userFileManager: UserFileManager,
private val userTracker: UserTracker,
private val taskViewFactory: Optional<TaskViewFactory>,
+ private val controlsSettingsRepository: ControlsSettingsRepository,
dumpManager: DumpManager
) : ControlsUiController, Dumpable {
@@ -354,7 +357,6 @@ class ControlsUiControllerImpl @Inject constructor (
} else {
items[0]
}
-
maybeUpdateSelectedItem(selectionItem)
createControlsSpaceFrame()
@@ -374,11 +376,20 @@ class ControlsUiControllerImpl @Inject constructor (
}
private fun createPanelView(componentName: ComponentName) {
- val pendingIntent = PendingIntent.getActivity(
+ val setting = controlsSettingsRepository
+ .allowActionOnTrivialControlsInLockscreen.value
+ val pendingIntent = PendingIntent.getActivityAsUser(
context,
0,
- Intent().setComponent(componentName),
- PendingIntent.FLAG_IMMUTABLE
+ Intent()
+ .setComponent(componentName)
+ .putExtra(
+ ControlsProviderService.EXTRA_LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS,
+ setting
+ ),
+ PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT,
+ null,
+ userTracker.userHandle
)
parent.requireViewById<View>(R.id.controls_scroll_view).visibility = View.GONE
@@ -698,6 +709,8 @@ class ControlsUiControllerImpl @Inject constructor (
println("hidden: $hidden")
println("selectedItem: $selectedItem")
println("lastSelections: $lastSelections")
+ println("setting: ${controlsSettingsRepository
+ .allowActionOnTrivialControlsInLockscreen.value}")
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index 705a110583db..8b4b30c89b14 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -32,6 +32,7 @@ import android.app.StatsManager;
import android.app.UiModeManager;
import android.app.WallpaperManager;
import android.app.admin.DevicePolicyManager;
+import android.app.job.JobScheduler;
import android.app.role.RoleManager;
import android.app.smartspace.SmartspaceManager;
import android.app.trust.TrustManager;
@@ -286,6 +287,12 @@ public class FrameworkServicesModule {
@Provides
@Singleton
+ static JobScheduler provideJobScheduler(Context context) {
+ return context.getSystemService(JobScheduler.class);
+ }
+
+ @Provides
+ @Singleton
static InteractionJankMonitor provideInteractionJankMonitor() {
return InteractionJankMonitor.getInstance();
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
index 81df4ed51d8d..267e0366b00e 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
@@ -87,7 +87,7 @@ public class FeatureFlagsDebug implements FeatureFlags {
new ServerFlagReader.ChangeListener() {
@Override
public void onChange() {
- mRestarter.restart();
+ mRestarter.restartSystemUI();
}
};
@@ -327,9 +327,7 @@ public class FeatureFlagsDebug implements FeatureFlags {
Log.i(TAG, "SystemUI Restart Suppressed");
return;
}
- Log.i(TAG, "Restarting SystemUI");
- // SysUI starts back when up exited. Is there a better way to do this?
- System.exit(0);
+ mRestarter.restartSystemUI();
}
private void restartAndroid(boolean requestSuppress) {
@@ -337,7 +335,7 @@ public class FeatureFlagsDebug implements FeatureFlags {
Log.i(TAG, "Android Restart Suppressed");
return;
}
- mRestarter.restart();
+ mRestarter.restartAndroid();
}
void setBooleanFlagInternal(Flag<?> flag, boolean value) {
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugRestarter.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugRestarter.kt
index 3d9f62768a37..069e6127aa30 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugRestarter.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugRestarter.kt
@@ -28,6 +28,8 @@ constructor(
private val systemExitRestarter: SystemExitRestarter,
) : Restarter {
+ private var androidRestartRequested = false
+
val observer =
object : WakefulnessLifecycle.Observer {
override fun onFinishedGoingToSleep() {
@@ -36,8 +38,18 @@ constructor(
}
}
- override fun restart() {
- Log.d(FeatureFlagsDebug.TAG, "Restart requested. Restarting on next screen off.")
+ override fun restartSystemUI() {
+ Log.d(FeatureFlagsDebug.TAG, "SystemUI Restart requested. Restarting on next screen off.")
+ scheduleRestart()
+ }
+
+ override fun restartAndroid() {
+ Log.d(FeatureFlagsDebug.TAG, "Android Restart requested. Restarting on next screen off.")
+ androidRestartRequested = true
+ scheduleRestart()
+ }
+
+ fun scheduleRestart() {
if (wakefulnessLifecycle.wakefulness == WakefulnessLifecycle.WAKEFULNESS_ASLEEP) {
restartNow()
} else {
@@ -46,6 +58,10 @@ constructor(
}
private fun restartNow() {
- systemExitRestarter.restart()
+ if (androidRestartRequested) {
+ systemExitRestarter.restartAndroid()
+ } else {
+ systemExitRestarter.restartSystemUI()
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt
index 7189f00457a4..b94d781154dd 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt
@@ -16,7 +16,9 @@
package com.android.systemui.flags
+import android.content.Intent
import com.android.systemui.CoreStartable
+import com.android.systemui.broadcast.BroadcastSender
import com.android.systemui.dump.DumpManager
import com.android.systemui.statusbar.commandline.CommandRegistry
import dagger.Binds
@@ -31,7 +33,8 @@ constructor(
dumpManager: DumpManager,
private val commandRegistry: CommandRegistry,
private val flagCommand: FlagCommand,
- private val featureFlags: FeatureFlagsDebug
+ private val featureFlags: FeatureFlagsDebug,
+ private val broadcastSender: BroadcastSender
) : CoreStartable {
init {
@@ -43,6 +46,8 @@ constructor(
override fun start() {
featureFlags.init()
commandRegistry.registerCommand(FlagCommand.FLAG_COMMAND) { flagCommand }
+ val intent = Intent(FlagManager.ACTION_SYSUI_STARTED)
+ broadcastSender.sendBroadcast(intent)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
index 3c83682210b6..8bddacc4e32c 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
@@ -61,7 +61,7 @@ public class FeatureFlagsRelease implements FeatureFlags {
new ServerFlagReader.ChangeListener() {
@Override
public void onChange() {
- mRestarter.restart();
+ mRestarter.restartSystemUI();
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseRestarter.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseRestarter.kt
index a3f0f665c056..7ff3876f5d4e 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseRestarter.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseRestarter.kt
@@ -34,35 +34,48 @@ constructor(
@Background private val bgExecutor: DelayableExecutor,
private val systemExitRestarter: SystemExitRestarter
) : Restarter {
- var shouldRestart = false
+ var listenersAdded = false
var pendingRestart: Runnable? = null
+ var androidRestartRequested = false
val observer =
object : WakefulnessLifecycle.Observer {
override fun onFinishedGoingToSleep() {
- maybeScheduleRestart()
+ scheduleRestart()
}
}
val batteryCallback =
object : BatteryController.BatteryStateChangeCallback {
override fun onBatteryLevelChanged(level: Int, pluggedIn: Boolean, charging: Boolean) {
- maybeScheduleRestart()
+ scheduleRestart()
}
}
- override fun restart() {
- Log.d(FeatureFlagsDebug.TAG, "Restart requested. Restarting when plugged in and idle.")
- if (!shouldRestart) {
- // Don't bother scheduling twice.
- shouldRestart = true
+ override fun restartSystemUI() {
+ Log.d(
+ FeatureFlagsDebug.TAG,
+ "SystemUI Restart requested. Restarting when plugged in and idle."
+ )
+ scheduleRestart()
+ }
+
+ override fun restartAndroid() {
+ Log.d(
+ FeatureFlagsDebug.TAG,
+ "Android Restart requested. Restarting when plugged in and idle."
+ )
+ androidRestartRequested = true
+ scheduleRestart()
+ }
+
+ private fun scheduleRestart() {
+ // Don't bother adding listeners twice.
+ if (!listenersAdded) {
+ listenersAdded = true
wakefulnessLifecycle.addObserver(observer)
batteryController.addCallback(batteryCallback)
- maybeScheduleRestart()
}
- }
-
- private fun maybeScheduleRestart() {
if (
wakefulnessLifecycle.wakefulness == WAKEFULNESS_ASLEEP && batteryController.isPluggedIn
) {
@@ -77,6 +90,10 @@ constructor(
private fun restartNow() {
Log.d(FeatureFlagsRelease.TAG, "Restarting due to systemui flag change")
- systemExitRestarter.restart()
+ if (androidRestartRequested) {
+ systemExitRestarter.restartAndroid()
+ } else {
+ systemExitRestarter.restartSystemUI()
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 7d9fdfa225de..4a7ff077c7b6 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -71,6 +71,9 @@ object Flags {
val NOTIFICATION_MEMORY_MONITOR_ENABLED =
releasedFlag(112, "notification_memory_monitor_enabled")
+ val NOTIFICATION_MEMORY_LOGGING_ENABLED =
+ unreleasedFlag(119, "notification_memory_logging_enabled", teamfood = true)
+
// TODO(b/254512731): Tracking Bug
@JvmField
val NOTIFICATION_DISMISSAL_FADE =
@@ -83,8 +86,7 @@ object Flags {
val SEMI_STABLE_SORT = unreleasedFlag(115, "semi_stable_sort", teamfood = true)
@JvmField
- val NOTIFICATION_GROUP_CORNER =
- unreleasedFlag(116, "notification_group_corner", teamfood = true)
+ val USE_ROUNDNESS_SOURCETYPES = unreleasedFlag(116, "use_roundness_sourcetype", teamfood = true)
// TODO(b/259217907)
@JvmField
@@ -92,6 +94,7 @@ object Flags {
unreleasedFlag(259217907, "notification_group_dismissal_animation", teamfood = true)
// TODO(b/257506350): Tracking Bug
+ @JvmField
val FSI_CHROME = unreleasedFlag(117, "fsi_chrome")
@JvmField
@@ -99,7 +102,7 @@ object Flags {
unreleasedFlag(259395680, "simplified_appear_fraction", teamfood = true)
// TODO(b/257315550): Tracking Bug
- val NO_HUN_FOR_OLD_WHEN = unreleasedFlag(118, "no_hun_for_old_when")
+ val NO_HUN_FOR_OLD_WHEN = unreleasedFlag(118, "no_hun_for_old_when", teamfood = true)
val FILTER_UNSEEN_NOTIFS_ON_KEYGUARD =
unreleasedFlag(254647461, "filter_unseen_notifs_on_keyguard", teamfood = true)
@@ -164,6 +167,13 @@ object Flags {
// TODO(b/256513609): Tracking Bug
@JvmField val ACTIVE_UNLOCK_CHIPBAR = releasedFlag(217, "active_unlock_chipbar")
+ /**
+ * Migrates control of the LightRevealScrim's reveal effect and amount from legacy code to the
+ * new KeyguardTransitionRepository.
+ */
+ @JvmField
+ val LIGHT_REVEAL_MIGRATION = unreleasedFlag(218, "light_reveal_migration", teamfood = true)
+
// 300 - power menu
// TODO(b/254512600): Tracking Bug
@JvmField val POWER_MENU_LITE = releasedFlag(300, "power_menu_lite")
@@ -198,9 +208,6 @@ object Flags {
"full_screen_user_switcher"
)
- // TODO(b/254512678): Tracking Bug
- @JvmField val NEW_FOOTER_ACTIONS = releasedFlag(507, "new_footer_actions")
-
// TODO(b/244064524): Tracking Bug
@JvmField val QS_SECONDARY_DATA_SUB_INFO = releasedFlag(508, "qs_secondary_data_sub_info")
@@ -220,7 +227,9 @@ object Flags {
val NEW_STATUS_BAR_WIFI_ICON_BACKEND = unreleasedFlag(609, "new_status_bar_wifi_icon_backend")
// TODO(b/256623670): Tracking Bug
- @JvmField val BATTERY_SHIELD_ICON = unreleasedFlag(610, "battery_shield_icon")
+ @JvmField
+ val BATTERY_SHIELD_ICON =
+ resourceBooleanFlag(610, R.bool.flag_battery_shield_icon, "battery_shield_icon")
// TODO(b/260881289): Tracking Bug
val NEW_STATUS_BAR_ICONS_DEBUG_COLORING =
@@ -427,6 +436,11 @@ object Flags {
val WARN_ON_BLOCKING_BINDER_TRANSACTIONS =
unreleasedFlag(2400, "warn_on_blocking_binder_transactions")
+ // 2500 - output switcher
+ // TODO(b/261538825): Tracking Bug
+ @JvmField
+ val OUTPUT_SWITCHER_ADVANCED_LAYOUT = unreleasedFlag(2500, "output_switcher_advanced_layout")
+
// TODO(b259590361): Tracking bug
val EXPERIMENTAL_FLAG = unreleasedFlag(2, "exp_flag_release")
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Restarter.kt b/packages/SystemUI/src/com/android/systemui/flags/Restarter.kt
index 8f095a24de94..ce8b821b1182 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Restarter.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Restarter.kt
@@ -16,5 +16,7 @@
package com.android.systemui.flags
interface Restarter {
- fun restart()
-} \ No newline at end of file
+ fun restartSystemUI()
+
+ fun restartAndroid()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/SystemExitRestarter.kt b/packages/SystemUI/src/com/android/systemui/flags/SystemExitRestarter.kt
index f1b1be47a84f..89daa6487fbc 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/SystemExitRestarter.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/SystemExitRestarter.kt
@@ -16,10 +16,19 @@
package com.android.systemui.flags
+import com.android.internal.statusbar.IStatusBarService
import javax.inject.Inject
-class SystemExitRestarter @Inject constructor() : Restarter {
- override fun restart() {
+class SystemExitRestarter
+@Inject
+constructor(
+ private val barService: IStatusBarService,
+) : Restarter {
+ override fun restartAndroid() {
+ barService.restart()
+ }
+
+ override fun restartSystemUI() {
System.exit(0)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index c4eac1c3c401..c0d6cc9fd2b8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -824,7 +824,11 @@ class KeyguardUnlockAnimationController @Inject constructor(
surfaceBehindEntryAnimator.cancel()
surfaceBehindAlpha = 1f
setSurfaceBehindAppearAmount(1f)
- launcherUnlockController?.setUnlockAmount(1f, false /* forceIfAnimating */)
+ try {
+ launcherUnlockController?.setUnlockAmount(1f, false /* forceIfAnimating */)
+ } catch (e: RemoteException) {
+ Log.e(TAG, "Remote exception in notifyFinishedKeyguardExitAnimation", e)
+ }
// That target is no longer valid since the animation finished, null it out.
surfaceBehindRemoteAnimationTargets = null
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 948239a58840..36c939d1156e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -2209,6 +2209,9 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
case START_KEYGUARD_EXIT_ANIM:
Trace.beginSection(
"KeyguardViewMediator#handleMessage START_KEYGUARD_EXIT_ANIM");
+ synchronized (KeyguardViewMediator.this) {
+ mHiding = true;
+ }
StartKeyguardExitAnimParams params = (StartKeyguardExitAnimParams) msg.obj;
mNotificationShadeWindowControllerLazy.get().batchApplyWindowLayoutParams(
() -> {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index 796f2b44effc..148792ba779b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -16,8 +16,11 @@
package com.android.systemui.keyguard.data.repository
+import android.graphics.Point
+import android.hardware.biometrics.BiometricSourceType
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.systemui.biometrics.AuthController
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.common.shared.model.Position
@@ -27,8 +30,8 @@ import com.android.systemui.doze.DozeMachine
import com.android.systemui.doze.DozeTransitionCallback
import com.android.systemui.doze.DozeTransitionListener
import com.android.systemui.keyguard.WakefulnessLifecycle
-import com.android.systemui.keyguard.WakefulnessLifecycle.Wakefulness
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
import com.android.systemui.keyguard.shared.model.DozeStateModel
import com.android.systemui.keyguard.shared.model.DozeTransitionModel
import com.android.systemui.keyguard.shared.model.StatusBarState
@@ -88,8 +91,8 @@ interface KeyguardRepository {
* enter to conserve battery when the device is locked and inactive.
*
* Note that it is possible for the system to be transitioning into doze while this flow still
- * returns `false`. In order to account for that, observers should also use the [dozeAmount]
- * flow to check if it's greater than `0`
+ * returns `false`. In order to account for that, observers should also use the
+ * [linearDozeAmount] flow to check if it's greater than `0`
*/
val isDozing: Flow<Boolean>
@@ -111,7 +114,7 @@ interface KeyguardRepository {
* happens during an animation/transition into doze mode. An observer would be wise to account
* for both flows if needed.
*/
- val dozeAmount: Flow<Float>
+ val linearDozeAmount: Flow<Float>
/** Doze state information, as it transitions */
val dozeTransitionModel: Flow<DozeTransitionModel>
@@ -120,11 +123,20 @@ interface KeyguardRepository {
val statusBarState: Flow<StatusBarState>
/** Observable for device wake/sleep state */
- val wakefulnessState: Flow<WakefulnessModel>
+ val wakefulness: Flow<WakefulnessModel>
/** Observable for biometric unlock modes */
val biometricUnlockState: Flow<BiometricUnlockModel>
+ /** Approximate location on the screen of the fingerprint sensor. */
+ val fingerprintSensorLocation: Flow<Point?>
+
+ /** Approximate location on the screen of the face unlock sensor/front facing camera. */
+ val faceSensorLocation: Flow<Point?>
+
+ /** Source of the most recent biometric unlock, such as fingerprint or face. */
+ val biometricUnlockSource: Flow<BiometricUnlockSource?>
+
/**
* Returns `true` if the keyguard is showing; `false` otherwise.
*
@@ -163,6 +175,7 @@ constructor(
private val keyguardStateController: KeyguardStateController,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
private val dozeTransitionListener: DozeTransitionListener,
+ private val authController: AuthController,
) : KeyguardRepository {
private val _animateBottomAreaDozingTransitions = MutableStateFlow(false)
override val animateBottomAreaDozingTransitions =
@@ -281,11 +294,11 @@ constructor(
}
.distinctUntilChanged()
- override val dozeAmount: Flow<Float> = conflatedCallbackFlow {
+ override val linearDozeAmount: Flow<Float> = conflatedCallbackFlow {
val callback =
object : StatusBarStateController.StateListener {
override fun onDozeAmountChanged(linear: Float, eased: Float) {
- trySendWithFailureLogging(eased, TAG, "updated dozeAmount")
+ trySendWithFailureLogging(linear, TAG, "updated dozeAmount")
}
}
@@ -348,56 +361,137 @@ constructor(
awaitClose { statusBarStateController.removeCallback(callback) }
}
- override val wakefulnessState: Flow<WakefulnessModel> = conflatedCallbackFlow {
+ override val biometricUnlockState: Flow<BiometricUnlockModel> = conflatedCallbackFlow {
+ fun dispatchUpdate() {
+ trySendWithFailureLogging(
+ biometricModeIntToObject(biometricUnlockController.mode),
+ TAG,
+ "biometric mode"
+ )
+ }
+
val callback =
+ object : BiometricUnlockController.BiometricModeListener {
+ override fun onModeChanged(@WakeAndUnlockMode mode: Int) {
+ dispatchUpdate()
+ }
+
+ override fun onResetMode() {
+ dispatchUpdate()
+ }
+ }
+
+ biometricUnlockController.addBiometricModeListener(callback)
+ dispatchUpdate()
+
+ awaitClose { biometricUnlockController.removeBiometricModeListener(callback) }
+ }
+
+ override val wakefulness: Flow<WakefulnessModel> = conflatedCallbackFlow {
+ val observer =
object : WakefulnessLifecycle.Observer {
override fun onStartedWakingUp() {
- trySendWithFailureLogging(
- WakefulnessModel.STARTING_TO_WAKE,
- TAG,
- "Wakefulness: starting to wake"
- )
+ dispatchNewState()
}
+
override fun onFinishedWakingUp() {
- trySendWithFailureLogging(WakefulnessModel.AWAKE, TAG, "Wakefulness: awake")
+ dispatchNewState()
+ }
+
+ override fun onPostFinishedWakingUp() {
+ dispatchNewState()
}
+
override fun onStartedGoingToSleep() {
+ dispatchNewState()
+ }
+
+ override fun onFinishedGoingToSleep() {
+ dispatchNewState()
+ }
+
+ private fun dispatchNewState() {
trySendWithFailureLogging(
- WakefulnessModel.STARTING_TO_SLEEP,
+ WakefulnessModel.fromWakefulnessLifecycle(wakefulnessLifecycle),
TAG,
- "Wakefulness: starting to sleep"
+ "updated wakefulness state"
)
}
- override fun onFinishedGoingToSleep() {
- trySendWithFailureLogging(WakefulnessModel.ASLEEP, TAG, "Wakefulness: asleep")
- }
}
- wakefulnessLifecycle.addObserver(callback)
+
+ wakefulnessLifecycle.addObserver(observer)
trySendWithFailureLogging(
- wakefulnessIntToObject(wakefulnessLifecycle.getWakefulness()),
+ WakefulnessModel.fromWakefulnessLifecycle(wakefulnessLifecycle),
TAG,
"initial wakefulness state"
)
- awaitClose { wakefulnessLifecycle.removeObserver(callback) }
+ awaitClose { wakefulnessLifecycle.removeObserver(observer) }
}
- override val biometricUnlockState: Flow<BiometricUnlockModel> = conflatedCallbackFlow {
+ override val fingerprintSensorLocation: Flow<Point?> = conflatedCallbackFlow {
+ fun sendFpLocation() {
+ trySendWithFailureLogging(
+ authController.fingerprintSensorLocation,
+ TAG,
+ "AuthController.Callback#onFingerprintLocationChanged"
+ )
+ }
+
val callback =
- object : BiometricUnlockController.BiometricModeListener {
- override fun onModeChanged(@WakeAndUnlockMode mode: Int) {
- trySendWithFailureLogging(biometricModeIntToObject(mode), TAG, "biometric mode")
+ object : AuthController.Callback {
+ override fun onFingerprintLocationChanged() {
+ sendFpLocation()
}
}
- biometricUnlockController.addBiometricModeListener(callback)
- trySendWithFailureLogging(
- biometricModeIntToObject(biometricUnlockController.getMode()),
- TAG,
- "initial biometric mode"
- )
+ authController.addCallback(callback)
+ sendFpLocation()
- awaitClose { biometricUnlockController.removeBiometricModeListener(callback) }
+ awaitClose { authController.removeCallback(callback) }
+ }
+
+ override val faceSensorLocation: Flow<Point?> = conflatedCallbackFlow {
+ fun sendSensorLocation() {
+ trySendWithFailureLogging(
+ authController.faceSensorLocation,
+ TAG,
+ "AuthController.Callback#onFingerprintLocationChanged"
+ )
+ }
+
+ val callback =
+ object : AuthController.Callback {
+ override fun onFaceSensorLocationChanged() {
+ sendSensorLocation()
+ }
+ }
+
+ authController.addCallback(callback)
+ sendSensorLocation()
+
+ awaitClose { authController.removeCallback(callback) }
+ }
+
+ override val biometricUnlockSource: Flow<BiometricUnlockSource?> = conflatedCallbackFlow {
+ val callback =
+ object : KeyguardUpdateMonitorCallback() {
+ override fun onBiometricAuthenticated(
+ userId: Int,
+ biometricSourceType: BiometricSourceType?,
+ isStrongBiometric: Boolean
+ ) {
+ trySendWithFailureLogging(
+ BiometricUnlockSource.fromBiometricSourceType(biometricSourceType),
+ TAG,
+ "onBiometricAuthenticated"
+ )
+ }
+ }
+
+ keyguardUpdateMonitor.registerCallback(callback)
+ trySendWithFailureLogging(null, TAG, "initial value")
+ awaitClose { keyguardUpdateMonitor.removeCallback(callback) }
}
override fun setAnimateDozingTransitions(animate: Boolean) {
@@ -423,16 +517,6 @@ constructor(
}
}
- private fun wakefulnessIntToObject(@Wakefulness value: Int): WakefulnessModel {
- return when (value) {
- 0 -> WakefulnessModel.ASLEEP
- 1 -> WakefulnessModel.STARTING_TO_WAKE
- 2 -> WakefulnessModel.AWAKE
- 3 -> WakefulnessModel.STARTING_TO_SLEEP
- else -> throw IllegalArgumentException("Invalid Wakefulness value: $value")
- }
- }
-
private fun biometricModeIntToObject(@WakeAndUnlockMode value: Int): BiometricUnlockModel {
return when (value) {
0 -> BiometricUnlockModel.NONE
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
index 0c725208e22d..26f853f3ad1c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
@@ -27,4 +27,7 @@ interface KeyguardRepositoryModule {
fun keyguardTransitionRepository(
impl: KeyguardTransitionRepositoryImpl
): KeyguardTransitionRepository
+
+ @Binds
+ fun lightRevealScrimRepository(impl: LightRevealScrimRepositoryImpl): LightRevealScrimRepository
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
index bce7d92cd8fb..5bb586e489e5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
@@ -116,6 +116,7 @@ class KeyguardTransitionRepositoryImpl @Inject constructor() : KeyguardTransitio
KeyguardState.LOCKSCREEN,
0f,
TransitionState.STARTED,
+ KeyguardTransitionRepositoryImpl::class.simpleName!!,
)
)
emitTransition(
@@ -124,6 +125,7 @@ class KeyguardTransitionRepositoryImpl @Inject constructor() : KeyguardTransitio
KeyguardState.LOCKSCREEN,
1f,
TransitionState.FINISHED,
+ KeyguardTransitionRepositoryImpl::class.simpleName!!,
)
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt
new file mode 100644
index 000000000000..a17481a9978b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.keyguard.data.repository
+
+import android.content.Context
+import android.graphics.Point
+import com.android.systemui.R
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
+import com.android.systemui.keyguard.shared.model.WakeSleepReason
+import com.android.systemui.statusbar.CircleReveal
+import com.android.systemui.statusbar.LiftReveal
+import com.android.systemui.statusbar.LightRevealEffect
+import com.android.systemui.statusbar.PowerButtonReveal
+import javax.inject.Inject
+import kotlin.math.max
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+
+val DEFAULT_REVEAL_EFFECT = LiftReveal
+
+/**
+ * Encapsulates state relevant to the light reveal scrim, the view used to reveal/hide screen
+ * contents during transitions between AOD and lockscreen/unlocked.
+ */
+interface LightRevealScrimRepository {
+
+ /**
+ * The reveal effect that should be used for the next lock/unlock. We switch between either the
+ * biometric unlock effect (if wake and unlocking) or the non-biometric effect, and position it
+ * at the current screen position of the appropriate sensor.
+ */
+ val revealEffect: Flow<LightRevealEffect>
+}
+
+@SysUISingleton
+class LightRevealScrimRepositoryImpl
+@Inject
+constructor(
+ keyguardRepository: KeyguardRepository,
+ val context: Context,
+) : LightRevealScrimRepository {
+
+ /** The reveal effect used if the device was locked/unlocked via the power button. */
+ private val powerButtonReveal =
+ PowerButtonReveal(
+ context.resources
+ .getDimensionPixelSize(R.dimen.physical_power_button_center_screen_location_y)
+ .toFloat()
+ )
+
+ /**
+ * Reveal effect to use for a fingerprint unlock. This is reconstructed if the fingerprint
+ * sensor location on the screen (in pixels) changes due to configuration changes.
+ */
+ private val fingerprintRevealEffect: Flow<LightRevealEffect?> =
+ keyguardRepository.fingerprintSensorLocation.map {
+ it?.let { constructCircleRevealFromPoint(it) }
+ }
+
+ /**
+ * Reveal effect to use for a face unlock. This is reconstructed if the face sensor/front camera
+ * location on the screen (in pixels) changes due to configuration changes.
+ */
+ private val faceRevealEffect: Flow<LightRevealEffect?> =
+ keyguardRepository.faceSensorLocation.map { it?.let { constructCircleRevealFromPoint(it) } }
+
+ /**
+ * The reveal effect we'll use for the next biometric unlock animation. We switch between the
+ * fingerprint/face unlock effect flows depending on the biometric unlock source.
+ */
+ private val biometricRevealEffect: Flow<LightRevealEffect?> =
+ keyguardRepository.biometricUnlockSource.flatMapLatest { source ->
+ when (source) {
+ BiometricUnlockSource.FINGERPRINT_SENSOR -> fingerprintRevealEffect
+ BiometricUnlockSource.FACE_SENSOR -> faceRevealEffect
+ else -> flowOf(null)
+ }
+ }
+
+ /** The reveal effect we'll use for the next non-biometric unlock (tap, power button, etc). */
+ private val nonBiometricRevealEffect: Flow<LightRevealEffect?> =
+ keyguardRepository.wakefulness.map { wakefulnessModel ->
+ val wakingUpFromPowerButton =
+ wakefulnessModel.isWakingUpOrAwake &&
+ wakefulnessModel.lastWakeReason == WakeSleepReason.POWER_BUTTON
+ val sleepingFromPowerButton =
+ !wakefulnessModel.isWakingUpOrAwake &&
+ wakefulnessModel.lastSleepReason == WakeSleepReason.POWER_BUTTON
+
+ if (wakingUpFromPowerButton || sleepingFromPowerButton) {
+ powerButtonReveal
+ } else {
+ LiftReveal
+ }
+ }
+
+ override val revealEffect =
+ combine(
+ keyguardRepository.biometricUnlockState,
+ biometricRevealEffect,
+ nonBiometricRevealEffect
+ ) { biometricUnlockState, biometricReveal, nonBiometricReveal ->
+
+ // Use the biometric reveal for any flavor of wake and unlocking.
+ when (biometricUnlockState) {
+ BiometricUnlockModel.WAKE_AND_UNLOCK,
+ BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING,
+ BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM -> biometricReveal
+ else -> nonBiometricReveal
+ }
+ ?: DEFAULT_REVEAL_EFFECT
+ }
+ .distinctUntilChanged()
+
+ private fun constructCircleRevealFromPoint(point: Point): LightRevealEffect {
+ return with(point) {
+ CircleReveal(
+ x,
+ y,
+ startRadius = 0,
+ endRadius =
+ max(max(x, context.display.width - x), max(y, context.display.height - y)),
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt
index 2dbacd5d3c69..f3d2905121bb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt
@@ -27,7 +27,6 @@ import com.android.systemui.keyguard.shared.model.TransitionInfo
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
@SysUISingleton
@@ -42,17 +41,14 @@ constructor(
override fun start() {
listenForTransitionToAodFromLockscreen()
- listenForTransitionToLockscreenFromAod()
+ listenForTransitionToLockscreenFromDozeStates()
}
private fun listenForTransitionToAodFromLockscreen() {
scope.launch {
keyguardInteractor
.dozeTransitionTo(DozeStateModel.DOZE_AOD)
- .sample(
- keyguardTransitionInteractor.startedKeyguardTransitionStep,
- { a, b -> Pair(a, b) }
- )
+ .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
.collect { pair ->
val (dozeToAod, lastStartedStep) = pair
if (lastStartedStep.to == KeyguardState.LOCKSCREEN) {
@@ -69,21 +65,19 @@ constructor(
}
}
- private fun listenForTransitionToLockscreenFromAod() {
+ private fun listenForTransitionToLockscreenFromDozeStates() {
+ val canGoToLockscreen = setOf(KeyguardState.AOD, KeyguardState.DOZING)
scope.launch {
keyguardInteractor
.dozeTransitionTo(DozeStateModel.FINISH)
- .sample(
- keyguardTransitionInteractor.startedKeyguardTransitionStep,
- { a, b -> Pair(a, b) }
- )
+ .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
.collect { pair ->
val (dozeToAod, lastStartedStep) = pair
- if (lastStartedStep.to == KeyguardState.AOD) {
+ if (canGoToLockscreen.contains(lastStartedStep.to)) {
keyguardTransitionRepository.startTransition(
TransitionInfo(
name,
- KeyguardState.AOD,
+ lastStartedStep.to,
KeyguardState.LOCKSCREEN,
getAnimator(),
)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodToGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodToGoneTransitionInteractor.kt
index 2a220fcd75a6..dad166f2b5e0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodToGoneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodToGoneTransitionInteractor.kt
@@ -21,9 +21,7 @@ import com.android.systemui.animation.Interpolators
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
-import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.WAKE_AND_UNLOCK
-import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM
-import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.Companion.isWakeAndUnlock
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionInfo
import com.android.systemui.util.kotlin.sample
@@ -42,9 +40,6 @@ constructor(
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
) : TransitionInteractor(AodToGoneTransitionInteractor::class.simpleName!!) {
- private val wakeAndUnlockModes =
- setOf(WAKE_AND_UNLOCK, WAKE_AND_UNLOCK_FROM_DREAM, WAKE_AND_UNLOCK_PULSING)
-
override fun start() {
scope.launch {
keyguardInteractor.biometricUnlockState
@@ -52,8 +47,7 @@ constructor(
.collect { pair ->
val (biometricUnlockState, keyguardState) = pair
if (
- keyguardState == KeyguardState.AOD &&
- wakeAndUnlockModes.contains(biometricUnlockState)
+ keyguardState == KeyguardState.AOD && isWakeAndUnlock(biometricUnlockState)
) {
keyguardTransitionRepository.startTransition(
TransitionInfo(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingLockscreenTransitionInteractor.kt
deleted file mode 100644
index 9cbf9eac686a..000000000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingLockscreenTransitionInteractor.kt
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.keyguard.domain.interactor
-
-import android.animation.ValueAnimator
-import com.android.systemui.animation.Interpolators
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
-import com.android.systemui.keyguard.shared.model.DozeStateModel
-import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.TransitionInfo
-import com.android.systemui.util.kotlin.sample
-import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.collect
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.launch
-
-@SysUISingleton
-class DreamingLockscreenTransitionInteractor
-@Inject
-constructor(
- @Application private val scope: CoroutineScope,
- private val keyguardInteractor: KeyguardInteractor,
- private val keyguardTransitionRepository: KeyguardTransitionRepository,
- private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
-) : TransitionInteractor(DreamingLockscreenTransitionInteractor::class.simpleName!!) {
-
- override fun start() {
- scope.launch {
- keyguardInteractor.isDreaming
- .sample(
- combine(
- keyguardInteractor.dozeTransitionModel,
- keyguardTransitionInteractor.finishedKeyguardState
- ) { a, b -> Pair(a, b) },
- { a, bc -> Triple(a, bc.first, bc.second) }
- )
- .collect { triple ->
- val (isDreaming, dozeTransitionModel, keyguardState) = triple
- // Dozing/AOD and dreaming have overlapping events. If the state remains in
- // FINISH, it means that doze mode is not running and DREAMING is ok to
- // commence.
- if (dozeTransitionModel.to == DozeStateModel.FINISH) {
- if (isDreaming && keyguardState == KeyguardState.LOCKSCREEN) {
- keyguardTransitionRepository.startTransition(
- TransitionInfo(
- name,
- KeyguardState.LOCKSCREEN,
- KeyguardState.DREAMING,
- getAnimator(),
- )
- )
- } else if (!isDreaming && keyguardState == KeyguardState.DREAMING) {
- keyguardTransitionRepository.startTransition(
- TransitionInfo(
- name,
- KeyguardState.DREAMING,
- KeyguardState.LOCKSCREEN,
- getAnimator(),
- )
- )
- }
- }
- }
- }
- }
-
- private fun getAnimator(): ValueAnimator {
- return ValueAnimator().apply {
- setInterpolator(Interpolators.LINEAR)
- setDuration(TRANSITION_DURATION_MS)
- }
- }
-
- companion object {
- private const val TRANSITION_DURATION_MS = 500L
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingToAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingToAodTransitionInteractor.kt
deleted file mode 100644
index 9e2b7241ade2..000000000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingToAodTransitionInteractor.kt
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.keyguard.domain.interactor
-
-import android.animation.ValueAnimator
-import com.android.systemui.animation.Interpolators
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
-import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.TransitionInfo
-import com.android.systemui.keyguard.shared.model.WakefulnessModel.Companion.isSleepingOrStartingToSleep
-import com.android.systemui.util.kotlin.sample
-import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.collect
-import kotlinx.coroutines.launch
-
-@SysUISingleton
-class DreamingToAodTransitionInteractor
-@Inject
-constructor(
- @Application private val scope: CoroutineScope,
- private val keyguardInteractor: KeyguardInteractor,
- private val keyguardTransitionRepository: KeyguardTransitionRepository,
- private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
-) : TransitionInteractor("DREAMING->AOD") {
-
- override fun start() {
- scope.launch {
- keyguardInteractor.wakefulnessState
- .sample(keyguardTransitionInteractor.finishedKeyguardState, { a, b -> Pair(a, b) })
- .collect { pair ->
- val (wakefulnessState, keyguardState) = pair
- if (
- isSleepingOrStartingToSleep(wakefulnessState) &&
- keyguardState == KeyguardState.DREAMING
- ) {
- keyguardTransitionRepository.startTransition(
- TransitionInfo(
- name,
- KeyguardState.DREAMING,
- KeyguardState.AOD,
- getAnimator(),
- )
- )
- }
- }
- }
- }
-
- private fun getAnimator(): ValueAnimator {
- return ValueAnimator().apply {
- setInterpolator(Interpolators.LINEAR)
- setDuration(TRANSITION_DURATION_MS)
- }
- }
-
- companion object {
- private const val TRANSITION_DURATION_MS = 300L
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingTransitionInteractor.kt
new file mode 100644
index 000000000000..b73ce9e5689c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingTransitionInteractor.kt
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.animation.ValueAnimator
+import com.android.systemui.animation.Interpolators
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.Companion.isWakeAndUnlock
+import com.android.systemui.keyguard.shared.model.DozeStateModel
+import com.android.systemui.keyguard.shared.model.DozeStateModel.Companion.isDozeOff
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.util.kotlin.sample
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.launch
+
+@SysUISingleton
+class DreamingTransitionInteractor
+@Inject
+constructor(
+ @Application private val scope: CoroutineScope,
+ private val keyguardInteractor: KeyguardInteractor,
+ private val keyguardTransitionRepository: KeyguardTransitionRepository,
+ private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
+) : TransitionInteractor(DreamingTransitionInteractor::class.simpleName!!) {
+
+ private val canDreamFrom =
+ setOf(KeyguardState.LOCKSCREEN, KeyguardState.GONE, KeyguardState.DOZING)
+
+ override fun start() {
+ listenForEntryToDreaming()
+ listenForDreamingToLockscreen()
+ listenForDreamingToGone()
+ listenForDreamingToDozing()
+ }
+
+ private fun listenForEntryToDreaming() {
+ scope.launch {
+ keyguardInteractor.isDreaming
+ .sample(
+ combine(
+ keyguardInteractor.dozeTransitionModel,
+ keyguardTransitionInteractor.finishedKeyguardState,
+ ::Pair
+ ),
+ ::toTriple
+ )
+ .collect { triple ->
+ val (isDreaming, dozeTransitionModel, keyguardState) = triple
+ // Dozing/AOD and dreaming have overlapping events. If the state remains in
+ // FINISH, it means that doze mode is not running and DREAMING is ok to
+ // commence.
+ if (
+ isDozeOff(dozeTransitionModel.to) &&
+ isDreaming &&
+ canDreamFrom.contains(keyguardState)
+ ) {
+ keyguardTransitionRepository.startTransition(
+ TransitionInfo(
+ name,
+ keyguardState,
+ KeyguardState.DREAMING,
+ getAnimator(),
+ )
+ )
+ }
+ }
+ }
+ }
+
+ private fun listenForDreamingToLockscreen() {
+ scope.launch {
+ keyguardInteractor.isDreaming
+ .sample(
+ combine(
+ keyguardInteractor.dozeTransitionModel,
+ keyguardTransitionInteractor.startedKeyguardTransitionStep,
+ ::Pair,
+ ),
+ ::toTriple
+ )
+ .collect { triple ->
+ val (isDreaming, dozeTransitionModel, lastStartedTransition) = triple
+ if (
+ isDozeOff(dozeTransitionModel.to) &&
+ !isDreaming &&
+ lastStartedTransition.to == KeyguardState.DREAMING
+ ) {
+ keyguardTransitionRepository.startTransition(
+ TransitionInfo(
+ name,
+ KeyguardState.DREAMING,
+ KeyguardState.LOCKSCREEN,
+ getAnimator(),
+ )
+ )
+ }
+ }
+ }
+ }
+
+ private fun listenForDreamingToGone() {
+ scope.launch {
+ keyguardInteractor.biometricUnlockState
+ .sample(keyguardTransitionInteractor.finishedKeyguardState, ::Pair)
+ .collect { pair ->
+ val (biometricUnlockState, keyguardState) = pair
+ if (
+ keyguardState == KeyguardState.DREAMING &&
+ isWakeAndUnlock(biometricUnlockState)
+ ) {
+ keyguardTransitionRepository.startTransition(
+ TransitionInfo(
+ name,
+ KeyguardState.DREAMING,
+ KeyguardState.GONE,
+ getAnimator(),
+ )
+ )
+ }
+ }
+ }
+ }
+
+ private fun listenForDreamingToDozing() {
+ scope.launch {
+ combine(
+ keyguardInteractor.dozeTransitionModel,
+ keyguardTransitionInteractor.finishedKeyguardState,
+ ::Pair
+ )
+ .collect { pair ->
+ val (dozeTransitionModel, keyguardState) = pair
+ if (
+ dozeTransitionModel.to == DozeStateModel.DOZE &&
+ keyguardState == KeyguardState.DREAMING
+ ) {
+ keyguardTransitionRepository.startTransition(
+ TransitionInfo(
+ name,
+ KeyguardState.DREAMING,
+ KeyguardState.DOZING,
+ getAnimator(),
+ )
+ )
+ }
+ }
+ }
+ }
+
+ private fun getAnimator(): ValueAnimator {
+ return ValueAnimator().apply {
+ setInterpolator(Interpolators.LINEAR)
+ setDuration(TRANSITION_DURATION_MS)
+ }
+ }
+
+ companion object {
+ private const val TRANSITION_DURATION_MS = 500L
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GoneAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GoneAodTransitionInteractor.kt
index 0e2a54c57bdb..a50e75909dd8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GoneAodTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GoneAodTransitionInteractor.kt
@@ -23,11 +23,10 @@ import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionInfo
-import com.android.systemui.keyguard.shared.model.WakefulnessModel
+import com.android.systemui.keyguard.shared.model.WakefulnessState
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
@SysUISingleton
@@ -38,17 +37,17 @@ constructor(
private val keyguardInteractor: KeyguardInteractor,
private val keyguardTransitionRepository: KeyguardTransitionRepository,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
-) : TransitionInteractor("GONE->AOD") {
+) : TransitionInteractor(GoneAodTransitionInteractor::class.simpleName!!) {
override fun start() {
scope.launch {
- keyguardInteractor.wakefulnessState
+ keyguardInteractor.wakefulnessModel
.sample(keyguardTransitionInteractor.finishedKeyguardState, { a, b -> Pair(a, b) })
.collect { pair ->
val (wakefulnessState, keyguardState) = pair
if (
keyguardState == KeyguardState.GONE &&
- wakefulnessState == WakefulnessModel.STARTING_TO_SLEEP
+ wakefulnessState.state == WakefulnessState.STARTING_TO_SLEEP
) {
keyguardTransitionRepository.startTransition(
TransitionInfo(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index 7cfd117d8eb4..6912e1d3558e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.domain.interactor
+import android.graphics.Point
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
@@ -41,7 +42,7 @@ constructor(
* The amount of doze the system is in, where `1.0` is fully dozing and `0.0` is not dozing at
* all.
*/
- val dozeAmount: Flow<Float> = repository.dozeAmount
+ val dozeAmount: Flow<Float> = repository.linearDozeAmount
/** Whether the system is in doze mode. */
val isDozing: Flow<Boolean> = repository.isDozing
/** Doze transition information. */
@@ -58,7 +59,7 @@ constructor(
/** Whether the bouncer is showing or not. */
val isBouncerShowing: Flow<Boolean> = repository.isBouncerShowing
/** The device wake/sleep state */
- val wakefulnessState: Flow<WakefulnessModel> = repository.wakefulnessState
+ val wakefulnessModel: Flow<WakefulnessModel> = repository.wakefulness
/** Observable for the [StatusBarState] */
val statusBarState: Flow<StatusBarState> = repository.statusBarState
/**
@@ -67,10 +68,15 @@ constructor(
*/
val biometricUnlockState: Flow<BiometricUnlockModel> = repository.biometricUnlockState
+ /** The approximate location on the screen of the fingerprint sensor, if one is available. */
+ val fingerprintSensorLocation: Flow<Point?> = repository.fingerprintSensorLocation
+
+ /** The approximate location on the screen of the face unlock sensor, if one is available. */
+ val faceSensorLocation: Flow<Point?> = repository.faceSensorLocation
+
fun dozeTransitionTo(state: DozeStateModel): Flow<DozeTransitionModel> {
return dozeTransitionModel.filter { it.to == state }
}
-
fun isKeyguardShowing(): Boolean {
return repository.isKeyguardShowing()
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
index 58a8093d49d2..a2661d76d90d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
@@ -37,7 +37,7 @@ constructor(
fun start() {
scope.launch {
- keyguardInteractor.wakefulnessState.collect { logger.v("WakefulnessState", it) }
+ keyguardInteractor.wakefulnessModel.collect { logger.v("WakefulnessModel", it) }
}
scope.launch {
@@ -46,6 +46,8 @@ constructor(
scope.launch { keyguardInteractor.isDozing.collect { logger.v("isDozing", it) } }
+ scope.launch { keyguardInteractor.isDreaming.collect { logger.v("isDreaming", it) } }
+
scope.launch {
interactor.finishedKeyguardTransitionStep.collect {
logger.i("Finished transition", it)
@@ -61,5 +63,9 @@ constructor(
scope.launch {
interactor.startedKeyguardTransitionStep.collect { logger.i("Started transition", it) }
}
+
+ scope.launch {
+ keyguardInteractor.dozeTransitionModel.collect { logger.i("Doze transition", it) }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
index 43dd358e4808..bb8b79a3aa6b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
@@ -43,8 +43,7 @@ constructor(
is LockscreenGoneTransitionInteractor -> Log.d(TAG, "Started $it")
is AodToGoneTransitionInteractor -> Log.d(TAG, "Started $it")
is BouncerToGoneTransitionInteractor -> Log.d(TAG, "Started $it")
- is DreamingLockscreenTransitionInteractor -> Log.d(TAG, "Started $it")
- is DreamingToAodTransitionInteractor -> Log.d(TAG, "Started $it")
+ is DreamingTransitionInteractor -> Log.d(TAG, "Started $it")
}
it.start()
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
new file mode 100644
index 000000000000..6e25200bc2f6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.LightRevealScrimRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.statusbar.LightRevealEffect
+import com.android.systemui.util.kotlin.sample
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.map
+
+@ExperimentalCoroutinesApi
+@SysUISingleton
+class LightRevealScrimInteractor
+@Inject
+constructor(
+ transitionRepository: KeyguardTransitionRepository,
+ transitionInteractor: KeyguardTransitionInteractor,
+ lightRevealScrimRepository: LightRevealScrimRepository,
+) {
+
+ /**
+ * Whenever a keyguard transition starts, sample the latest reveal effect from the repository
+ * and use that for the starting transition.
+ *
+ * We can't simply use the nextRevealEffect since the effect may change midway through a
+ * transition, but we don't want to change effects part way through. For example, if we're using
+ * a CircleReveal to animate a biometric unlock, but the biometric unlock mode changes to NONE
+ * from WAKE_AND_UNLOCK before the unlock animation ends, we don't want to end up switching to a
+ * LiftReveal.
+ */
+ val lightRevealEffect: Flow<LightRevealEffect> =
+ transitionInteractor.startedKeyguardTransitionStep.sample(
+ lightRevealScrimRepository.revealEffect
+ )
+
+ /**
+ * The reveal amount to use for the light reveal scrim, which is derived from the keyguard
+ * transition steps.
+ */
+ val revealAmount: Flow<Float> =
+ transitionRepository.transitions
+ // Only listen to transitions that change the reveal amount.
+ .filter { willTransitionAffectRevealAmount(it) }
+ // Use the transition amount as the reveal amount, inverting it if we're transitioning
+ // to a non-revealed (hidden) state.
+ .map { step -> if (willBeRevealedInState(step.to)) step.value else 1f - step.value }
+
+ companion object {
+
+ /**
+ * Whether the transition requires a change in the reveal amount of the light reveal scrim.
+ * If not, we don't care about the transition and don't need to listen to it.
+ */
+ fun willTransitionAffectRevealAmount(transition: TransitionStep): Boolean {
+ return willBeRevealedInState(transition.from) != willBeRevealedInState(transition.to)
+ }
+
+ /**
+ * Whether the light reveal scrim will be fully revealed (revealAmount = 1.0f) in the given
+ * state after the transition is complete. If false, scrim will be fully hidden.
+ */
+ fun willBeRevealedInState(state: KeyguardState): Boolean {
+ return when (state) {
+ KeyguardState.OFF -> false
+ KeyguardState.DOZING -> false
+ KeyguardState.AOD -> false
+ KeyguardState.DREAMING -> true
+ KeyguardState.BOUNCER -> true
+ KeyguardState.LOCKSCREEN -> true
+ KeyguardState.GONE -> true
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt
index 3bb8241257cc..3218f9699f35 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt
@@ -25,7 +25,7 @@ import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.StatusBarState.SHADE_LOCKED
import com.android.systemui.keyguard.shared.model.TransitionInfo
import com.android.systemui.keyguard.shared.model.TransitionState
-import com.android.systemui.keyguard.shared.model.WakefulnessModel
+import com.android.systemui.keyguard.shared.model.WakefulnessState
import com.android.systemui.shade.data.repository.ShadeRepository
import com.android.systemui.util.kotlin.sample
import java.util.UUID
@@ -58,22 +58,26 @@ constructor(
keyguardInteractor.isBouncerShowing
.sample(
combine(
- keyguardInteractor.wakefulnessState,
+ keyguardInteractor.wakefulnessModel,
keyguardTransitionInteractor.startedKeyguardTransitionStep,
- ) { a, b ->
- Pair(a, b)
- },
- { a, bc -> Triple(a, bc.first, bc.second) }
- )
- .collect { triple ->
- val (isBouncerShowing, wakefulnessState, lastStartedTransitionStep) = triple
+ ) { wakefulnessModel, transitionStep ->
+ Pair(wakefulnessModel, transitionStep)
+ }
+ ) { bouncerShowing, wakefulnessAndTransition ->
+ Triple(
+ bouncerShowing,
+ wakefulnessAndTransition.first,
+ wakefulnessAndTransition.second
+ )
+ }
+ .collect { (isBouncerShowing, wakefulnessState, lastStartedTransitionStep) ->
if (
!isBouncerShowing && lastStartedTransitionStep.to == KeyguardState.BOUNCER
) {
val to =
if (
- wakefulnessState == WakefulnessModel.STARTING_TO_SLEEP ||
- wakefulnessState == WakefulnessModel.ASLEEP
+ wakefulnessState.state == WakefulnessState.STARTING_TO_SLEEP ||
+ wakefulnessState.state == WakefulnessState.ASLEEP
) {
KeyguardState.AOD
} else {
@@ -100,14 +104,17 @@ constructor(
combine(
keyguardTransitionInteractor.finishedKeyguardState,
keyguardInteractor.statusBarState,
- ) { a, b ->
- Pair(a, b)
- },
- { a, bc -> Triple(a, bc.first, bc.second) }
- )
- .collect { triple ->
- val (shadeModel, keyguardState, statusBarState) = triple
-
+ ) { finishedKeyguardState, statusBarState ->
+ Pair(finishedKeyguardState, statusBarState)
+ }
+ ) { shadeModel, keyguardStateAndStatusBarState ->
+ Triple(
+ shadeModel,
+ keyguardStateAndStatusBarState.first,
+ keyguardStateAndStatusBarState.second
+ )
+ }
+ .collect { (shadeModel, keyguardState, statusBarState) ->
val id = transitionId
if (id != null) {
// An existing `id` means a transition is started, and calls to
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenGoneTransitionInteractor.kt
index 4100f7a8413a..95d96025cf4a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenGoneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenGoneTransitionInteractor.kt
@@ -37,15 +37,15 @@ constructor(
private val keyguardInteractor: KeyguardInteractor,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
private val keyguardTransitionRepository: KeyguardTransitionRepository,
-) : TransitionInteractor("LOCKSCREEN->GONE") {
+) : TransitionInteractor(LockscreenGoneTransitionInteractor::class.simpleName!!) {
override fun start() {
scope.launch {
keyguardInteractor.isKeyguardGoingAway
- .sample(keyguardTransitionInteractor.finishedKeyguardState, { a, b -> Pair(a, b) })
+ .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
.collect { pair ->
- val (isKeyguardGoingAway, keyguardState) = pair
- if (!isKeyguardGoingAway && keyguardState == KeyguardState.LOCKSCREEN) {
+ val (isKeyguardGoingAway, lastStartedStep) = pair
+ if (isKeyguardGoingAway && lastStartedStep.to == KeyguardState.LOCKSCREEN) {
keyguardTransitionRepository.startTransition(
TransitionInfo(
name,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt
index dbffeab436a4..5f63ae765854 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt
@@ -52,13 +52,5 @@ abstract class StartKeyguardTransitionModule {
@IntoSet
abstract fun lockscreenGone(impl: LockscreenGoneTransitionInteractor): TransitionInteractor
- @Binds
- @IntoSet
- abstract fun dreamingLockscreen(
- impl: DreamingLockscreenTransitionInteractor
- ): TransitionInteractor
-
- @Binds
- @IntoSet
- abstract fun dreamingToAod(impl: DreamingToAodTransitionInteractor): TransitionInteractor
+ @Binds @IntoSet abstract fun dreaming(impl: DreamingTransitionInteractor): TransitionInteractor
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
index a2a46d9e3a71..08ad3d5bdbf6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
@@ -29,4 +29,6 @@ package com.android.systemui.keyguard.domain.interactor
sealed class TransitionInteractor(val name: String) {
abstract fun start()
+
+ fun <A, B, C> toTriple(a: A, bc: Pair<B, C>) = Triple(a, bc.first, bc.second)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BiometricUnlockModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BiometricUnlockModel.kt
index db709b476c5d..8fe6309fc005 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BiometricUnlockModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BiometricUnlockModel.kt
@@ -46,5 +46,14 @@ enum class BiometricUnlockModel {
/** When bouncer is visible and will be dismissed. */
DISMISS_BOUNCER,
/** Mode in which fingerprint wakes and unlocks the device from a dream. */
- WAKE_AND_UNLOCK_FROM_DREAM,
+ WAKE_AND_UNLOCK_FROM_DREAM;
+
+ companion object {
+ private val wakeAndUnlockModes =
+ setOf(WAKE_AND_UNLOCK, WAKE_AND_UNLOCK_FROM_DREAM, WAKE_AND_UNLOCK_PULSING)
+
+ fun isWakeAndUnlock(model: BiometricUnlockModel): Boolean {
+ return wakeAndUnlockModes.contains(model)
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BiometricUnlockSource.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BiometricUnlockSource.kt
new file mode 100644
index 000000000000..b403416c572c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BiometricUnlockSource.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.shared.model
+
+import android.hardware.biometrics.BiometricSourceType
+
+/** Biometric unlock sensor sources, which we use to play sensor-specific animations. */
+enum class BiometricUnlockSource {
+ /** The unlock was initiated by a fingerprint sensor authentication. */
+ FINGERPRINT_SENSOR,
+
+ /** The unlock was initiated by the front-facing camera or a nearby sensor. */
+ FACE_SENSOR;
+
+ companion object {
+ fun fromBiometricSourceType(type: BiometricSourceType?): BiometricUnlockSource? {
+ return when (type) {
+ BiometricSourceType.FINGERPRINT -> FINGERPRINT_SENSOR
+ BiometricSourceType.FACE -> FACE_SENSOR
+ BiometricSourceType.IRIS -> FACE_SENSOR
+ else -> null
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/DozeStateModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/DozeStateModel.kt
index 7039188d9e96..65b7cf732f9d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/DozeStateModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/DozeStateModel.kt
@@ -42,5 +42,11 @@ enum class DozeStateModel {
/** AOD, prox is near, transitions to DOZE_AOD_PAUSED after a timeout. */
DOZE_AOD_PAUSING,
/** Always-on doze. Device is awake, showing docking UI and listening for pulse triggers. */
- DOZE_AOD_DOCKED
+ DOZE_AOD_DOCKED;
+
+ companion object {
+ fun isDozeOff(model: DozeStateModel): Boolean {
+ return model == UNINITIALIZED || model == FINISH
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakeSleepReason.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakeSleepReason.kt
new file mode 100644
index 000000000000..b32597d5cff0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakeSleepReason.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.shared.model
+
+import android.os.PowerManager
+
+/** The reason we're waking up or going to sleep, such as pressing the power button. */
+enum class WakeSleepReason {
+ /** The physical power button was pressed to wake up or sleep the device. */
+ POWER_BUTTON,
+
+ /** Something else happened to wake up or sleep the device. */
+ OTHER;
+
+ companion object {
+ fun fromPowerManagerWakeReason(reason: Int): WakeSleepReason {
+ return when (reason) {
+ PowerManager.WAKE_REASON_POWER_BUTTON -> POWER_BUTTON
+ else -> OTHER
+ }
+ }
+
+ fun fromPowerManagerSleepReason(reason: Int): WakeSleepReason {
+ return when (reason) {
+ PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON -> POWER_BUTTON
+ else -> OTHER
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt
index 92040f4f0348..03dee0045c10 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt
@@ -15,24 +15,34 @@
*/
package com.android.systemui.keyguard.shared.model
-/** Model device wakefulness states. */
-enum class WakefulnessModel {
- /** The device is asleep and not interactive. */
- ASLEEP,
- /** Received a signal that the device is beginning to wake up. */
- STARTING_TO_WAKE,
- /** Device is now fully awake and interactive. */
- AWAKE,
- /** Signal that the device is now going to sleep. */
- STARTING_TO_SLEEP;
+import com.android.systemui.keyguard.WakefulnessLifecycle
+/** Model device wakefulness states. */
+data class WakefulnessModel(
+ val state: WakefulnessState,
+ val isWakingUpOrAwake: Boolean,
+ val lastWakeReason: WakeSleepReason,
+ val lastSleepReason: WakeSleepReason,
+) {
companion object {
fun isSleepingOrStartingToSleep(model: WakefulnessModel): Boolean {
- return model == ASLEEP || model == STARTING_TO_SLEEP
+ return model.state == WakefulnessState.ASLEEP ||
+ model.state == WakefulnessState.STARTING_TO_SLEEP
}
fun isWakingOrStartingToWake(model: WakefulnessModel): Boolean {
- return model == AWAKE || model == STARTING_TO_WAKE
+ return model.state == WakefulnessState.AWAKE ||
+ model.state == WakefulnessState.STARTING_TO_WAKE
+ }
+
+ fun fromWakefulnessLifecycle(wakefulnessLifecycle: WakefulnessLifecycle): WakefulnessModel {
+ return WakefulnessModel(
+ WakefulnessState.fromWakefulnessLifecycleInt(wakefulnessLifecycle.wakefulness),
+ wakefulnessLifecycle.wakefulness == WakefulnessLifecycle.WAKEFULNESS_WAKING ||
+ wakefulnessLifecycle.wakefulness == WakefulnessLifecycle.WAKEFULNESS_AWAKE,
+ WakeSleepReason.fromPowerManagerWakeReason(wakefulnessLifecycle.lastWakeReason),
+ WakeSleepReason.fromPowerManagerSleepReason(wakefulnessLifecycle.lastSleepReason),
+ )
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessState.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessState.kt
new file mode 100644
index 000000000000..6791d88f16d1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessState.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.shared.model
+
+import com.android.systemui.keyguard.WakefulnessLifecycle
+
+enum class WakefulnessState {
+ /** The device is asleep and not interactive. */
+ ASLEEP,
+ /** Received a signal that the device is beginning to wake up. */
+ STARTING_TO_WAKE,
+ /** Device is now fully awake and interactive. */
+ AWAKE,
+ /** Signal that the device is now going to sleep. */
+ STARTING_TO_SLEEP;
+
+ companion object {
+ fun fromWakefulnessLifecycleInt(
+ @WakefulnessLifecycle.Wakefulness value: Int
+ ): WakefulnessState {
+ return when (value) {
+ WakefulnessLifecycle.WAKEFULNESS_ASLEEP -> ASLEEP
+ WakefulnessLifecycle.WAKEFULNESS_WAKING -> STARTING_TO_WAKE
+ WakefulnessLifecycle.WAKEFULNESS_AWAKE -> AWAKE
+ WakefulnessLifecycle.WAKEFULNESS_GOING_TO_SLEEP -> STARTING_TO_SLEEP
+ else -> throw IllegalArgumentException("Invalid Wakefulness value: $value")
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/LightRevealScrimViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/LightRevealScrimViewBinder.kt
new file mode 100644
index 000000000000..f1da8826a22c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/LightRevealScrimViewBinder.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.binder
+
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.keyguard.ui.viewmodel.LightRevealScrimViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.statusbar.LightRevealScrim
+import kotlinx.coroutines.launch
+
+object LightRevealScrimViewBinder {
+ @JvmStatic
+ fun bind(revealScrim: LightRevealScrim, viewModel: LightRevealScrimViewModel) {
+ revealScrim.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ launch {
+ viewModel.revealAmount.collect { amount -> revealScrim.revealAmount = amount }
+ }
+
+ launch {
+ viewModel.lightRevealEffect.collect { effect ->
+ revealScrim.revealEffect = effect
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LightRevealScrimViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LightRevealScrimViewModel.kt
new file mode 100644
index 000000000000..a46d441613ac
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LightRevealScrimViewModel.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.keyguard.domain.interactor.LightRevealScrimInteractor
+import com.android.systemui.statusbar.LightRevealEffect
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * Models UI state for the light reveal scrim, which is used during screen on and off animations to
+ * draw a gradient that reveals/hides the contents of the screen.
+ */
+class LightRevealScrimViewModel @Inject constructor(interactor: LightRevealScrimInteractor) {
+ val lightRevealEffect: Flow<LightRevealEffect> = interactor.lightRevealEffect
+ val revealAmount: Flow<Float> = interactor.revealAmount
+}
diff --git a/packages/SystemUI/src/com/android/systemui/log/SessionTracker.java b/packages/SystemUI/src/com/android/systemui/log/SessionTracker.java
index c7e4c5e93090..b98a92ff8600 100644
--- a/packages/SystemUI/src/com/android/systemui/log/SessionTracker.java
+++ b/packages/SystemUI/src/com/android/systemui/log/SessionTracker.java
@@ -49,7 +49,9 @@ import javax.inject.Inject;
@SysUISingleton
public class SessionTracker implements CoreStartable {
private static final String TAG = "SessionTracker";
- private static final boolean DEBUG = false;
+
+ // To enable logs: `adb shell setprop log.tag.SessionTracker DEBUG` & restart sysui
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
// At most 20 bits: ~1m possibilities, ~0.5% probability of collision in 100 values
private final InstanceIdSequence mInstanceIdGenerator = new InstanceIdSequence(1 << 20);
@@ -81,8 +83,8 @@ public class SessionTracker implements CoreStartable {
mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
mKeyguardStateController.addCallback(mKeyguardStateCallback);
- mKeyguardSessionStarted = mKeyguardStateController.isShowing();
- if (mKeyguardSessionStarted) {
+ if (mKeyguardStateController.isShowing()) {
+ mKeyguardSessionStarted = true;
startSession(SESSION_KEYGUARD);
}
}
@@ -136,12 +138,11 @@ public class SessionTracker implements CoreStartable {
new KeyguardUpdateMonitorCallback() {
@Override
public void onStartedGoingToSleep(int why) {
- // we need to register to the KeyguardUpdateMonitor lifecycle b/c it gets called
- // before the WakefulnessLifecycle
if (mKeyguardSessionStarted) {
- return;
+ endSession(SESSION_KEYGUARD);
}
+ // Start a new session whenever the device goes to sleep
mKeyguardSessionStarted = true;
startSession(SESSION_KEYGUARD);
}
@@ -154,6 +155,9 @@ public class SessionTracker implements CoreStartable {
boolean wasSessionStarted = mKeyguardSessionStarted;
boolean keyguardShowing = mKeyguardStateController.isShowing();
if (keyguardShowing && !wasSessionStarted) {
+ // the keyguard can start showing without the device going to sleep (ie: lockdown
+ // from the power button), so we start a new keyguard session when the keyguard is
+ // newly shown in addition to when the device starts going to sleep
mKeyguardSessionStarted = true;
startSession(SESSION_KEYGUARD);
} else if (!keyguardShowing && wasSessionStarted) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
index 3012bb41445e..2dd339d409a6 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
@@ -422,6 +422,7 @@ class MediaDataManager(
appUid = appUid
)
mediaEntries.put(packageName, resumeData)
+ logSingleVsMultipleMediaAdded(appUid, packageName, instanceId)
logger.logResumeMediaAdded(appUid, packageName, instanceId)
}
backgroundExecutor.execute {
@@ -812,6 +813,7 @@ class MediaDataManager(
val appUid = appInfo?.uid ?: Process.INVALID_UID
if (logEvent) {
+ logSingleVsMultipleMediaAdded(appUid, sbn.packageName, instanceId)
logger.logActiveMediaAdded(appUid, sbn.packageName, instanceId, playbackLocation)
} else if (playbackLocation != currentEntry?.playbackLocation) {
logger.logPlaybackLocationChange(appUid, sbn.packageName, instanceId, playbackLocation)
@@ -855,6 +857,20 @@ class MediaDataManager(
}
}
+ private fun logSingleVsMultipleMediaAdded(
+ appUid: Int,
+ packageName: String,
+ instanceId: InstanceId
+ ) {
+ if (mediaEntries.size == 1) {
+ logger.logSingleMediaPlayerInCarousel(appUid, packageName, instanceId)
+ } else if (mediaEntries.size == 2) {
+ // Since this method is only called when there is a new media session added.
+ // logging needed once there is more than one media session in carousel.
+ logger.logMultipleMediaPlayersInCarousel(appUid, packageName, instanceId)
+ }
+ }
+
private fun getAppInfoFromPackage(packageName: String): ApplicationInfo? {
try {
return context.packageManager.getApplicationInfo(packageName, 0)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
index 21e64e28ff19..df8fb9149f30 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
@@ -458,7 +458,9 @@ public class MediaControlPanel {
if (mMediaViewHolder == null) {
return;
}
- Trace.beginSection("MediaControlPanel#bindPlayer<" + key + ">");
+ if (Trace.isEnabled()) {
+ Trace.traceBegin(Trace.TRACE_TAG_APP, "MediaControlPanel#bindPlayer<" + key + ">");
+ }
mKey = key;
mMediaData = data;
MediaSession.Token token = data.getToken();
@@ -938,19 +940,9 @@ public class MediaControlPanel {
if (mIsSeekBarEnabled) {
return ConstraintSet.VISIBLE;
}
- // If disabled and "neighbours" are visible, set progress bar to INVISIBLE instead of GONE
- // so layout weights still work.
- return areAnyExpandedBottomActionsVisible() ? ConstraintSet.INVISIBLE : ConstraintSet.GONE;
- }
-
- private boolean areAnyExpandedBottomActionsVisible() {
- ConstraintSet expandedSet = mMediaViewController.getExpandedLayout();
- for (int id : MediaViewHolder.Companion.getExpandedBottomActionIds()) {
- if (expandedSet.getVisibility(id) == ConstraintSet.VISIBLE) {
- return true;
- }
- }
- return false;
+ // Set progress bar to INVISIBLE to keep the positions of text and buttons similar to the
+ // original positions when seekbar is enabled.
+ return ConstraintSet.INVISIBLE;
}
private void setGenericButton(
@@ -1179,8 +1171,10 @@ public class MediaControlPanel {
return;
}
- Trace.beginSection(
- "MediaControlPanel#bindRecommendation<" + data.getPackageName() + ">");
+ if (Trace.isEnabled()) {
+ Trace.traceBegin(Trace.TRACE_TAG_APP,
+ "MediaControlPanel#bindRecommendation<" + data.getPackageName() + ">");
+ }
mRecommendationData = data;
mSmartspaceId = SmallHash.hash(data.getTargetId());
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaUiEventLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaUiEventLogger.kt
index 3ad8c21e8a1e..ea943be85f21 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaUiEventLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaUiEventLogger.kt
@@ -213,6 +213,24 @@ class MediaUiEventLogger @Inject constructor(private val logger: UiEventLogger)
instanceId
)
}
+
+ fun logSingleMediaPlayerInCarousel(uid: Int, packageName: String, instanceId: InstanceId) {
+ logger.logWithInstanceId(
+ MediaUiEvent.MEDIA_CAROUSEL_SINGLE_PLAYER,
+ uid,
+ packageName,
+ instanceId
+ )
+ }
+
+ fun logMultipleMediaPlayersInCarousel(uid: Int, packageName: String, instanceId: InstanceId) {
+ logger.logWithInstanceId(
+ MediaUiEvent.MEDIA_CAROUSEL_MULTIPLE_PLAYERS,
+ uid,
+ packageName,
+ instanceId
+ )
+ }
}
enum class MediaUiEvent(val metricId: Int) : UiEventLogger.UiEventEnum {
@@ -269,7 +287,11 @@ enum class MediaUiEvent(val metricId: Int) : UiEventLogger.UiEventEnum {
@UiEvent(doc = "User tapped on a media recommendation card")
MEDIA_RECOMMENDATION_CARD_TAP(1045),
@UiEvent(doc = "User opened the broadcast dialog from a media control")
- MEDIA_OPEN_BROADCAST_DIALOG(1079);
+ MEDIA_OPEN_BROADCAST_DIALOG(1079),
+ @UiEvent(doc = "The media carousel contains one media player card")
+ MEDIA_CAROUSEL_SINGLE_PLAYER(1244),
+ @UiEvent(doc = "The media carousel contains multiple media player cards")
+ MEDIA_CAROUSEL_MULTIPLE_PLAYERS(1245);
override fun getId() = metricId
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
index ee5956105b7b..3dccae03e906 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
@@ -16,7 +16,6 @@
package com.android.systemui.media.dialog;
-import android.annotation.DrawableRes;
import android.content.res.ColorStateList;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
@@ -122,17 +121,19 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
// Set different layout for each device
if (device.isMutingExpectedDevice()
&& !mController.isCurrentConnectedDeviceRemote()) {
- updateTitleIcon(R.drawable.media_output_icon_volume,
- mController.getColorItemContent());
+ if (!mController.isAdvancedLayoutSupported()) {
+ updateTitleIcon(R.drawable.media_output_icon_volume,
+ mController.getColorItemContent());
+ }
initMutingExpectedDevice();
mCurrentActivePosition = position;
- updateContainerClickListener(v -> onItemClick(v, device));
+ updateFullItemClickListener(v -> onItemClick(v, device));
setSingleLineLayout(getItemTitle(device));
} else if (device.getState() == MediaDeviceState.STATE_CONNECTING_FAILED) {
setUpDeviceIcon(device);
updateConnectionFailedStatusIcon();
mSubTitleText.setText(R.string.media_output_dialog_connect_failed);
- updateContainerClickListener(v -> onItemClick(v, device));
+ updateFullItemClickListener(v -> onItemClick(v, device));
setTwoLineLayout(device, false /* bFocused */, false /* showSeekBar */,
false /* showProgressBar */, true /* showSubtitle */,
true /* showStatus */);
@@ -146,8 +147,10 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
&& isDeviceIncluded(mController.getSelectedMediaDevice(), device)) {
boolean isDeviceDeselectable = isDeviceIncluded(
mController.getDeselectableMediaDevice(), device);
- updateTitleIcon(R.drawable.media_output_icon_volume,
- mController.getColorItemContent());
+ if (!mController.isAdvancedLayoutSupported()) {
+ updateTitleIcon(R.drawable.media_output_icon_volume,
+ mController.getColorItemContent());
+ }
updateGroupableCheckBox(true, isDeviceDeselectable, device);
updateEndClickArea(device, isDeviceDeselectable);
setUpContentDescriptionForView(mContainerLayout, false, device);
@@ -161,8 +164,22 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
&& !mController.isCurrentConnectedDeviceRemote()) {
// mark as disconnected and set special click listener
setUpDeviceIcon(device);
- updateContainerClickListener(v -> cancelMuteAwaitConnection());
+ updateFullItemClickListener(v -> cancelMuteAwaitConnection());
setSingleLineLayout(getItemTitle(device));
+ } else if (mController.isCurrentConnectedDeviceRemote()
+ && !mController.getSelectableMediaDevice().isEmpty()
+ && mController.isAdvancedLayoutSupported()) {
+ //If device is connected and there's other selectable devices, layout as
+ // one of selected devices.
+ boolean isDeviceDeselectable = isDeviceIncluded(
+ mController.getDeselectableMediaDevice(), device);
+ updateGroupableCheckBox(true, isDeviceDeselectable, device);
+ updateEndClickArea(device, isDeviceDeselectable);
+ setUpContentDescriptionForView(mContainerLayout, false, device);
+ setSingleLineLayout(getItemTitle(device), true /* showSeekBar */,
+ false /* showProgressBar */, true /* showCheckBox */,
+ true /* showEndTouchArea */);
+ initSeekbar(device, isCurrentSeekbarInvisible);
} else {
updateTitleIcon(R.drawable.media_output_icon_volume,
mController.getColorItemContent());
@@ -176,14 +193,19 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
} else if (isDeviceIncluded(mController.getSelectableMediaDevice(), device)) {
setUpDeviceIcon(device);
updateGroupableCheckBox(false, true, device);
- updateContainerClickListener(v -> onGroupActionTriggered(true, device));
+ if (mController.isAdvancedLayoutSupported()) {
+ updateEndClickArea(device, true);
+ }
+ updateFullItemClickListener(mController.isAdvancedLayoutSupported()
+ ? v -> onItemClick(v, device)
+ : v -> onGroupActionTriggered(true, device));
setSingleLineLayout(getItemTitle(device), false /* showSeekBar */,
false /* showProgressBar */, true /* showCheckBox */,
true /* showEndTouchArea */);
} else {
setUpDeviceIcon(device);
setSingleLineLayout(getItemTitle(device));
- updateContainerClickListener(v -> onItemClick(v, device));
+ updateFullItemClickListener(v -> onItemClick(v, device));
}
}
}
@@ -214,6 +236,11 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
isDeviceDeselectable ? (v) -> mCheckBox.performClick() : null);
mEndTouchArea.setImportantForAccessibility(
View.IMPORTANT_FOR_ACCESSIBILITY_YES);
+ if (mController.isAdvancedLayoutSupported()) {
+ mEndTouchArea.getBackground().setColorFilter(
+ new PorterDuffColorFilter(mController.getColorItemBackground(),
+ PorterDuff.Mode.SRC_IN));
+ }
setUpContentDescriptionForView(mEndTouchArea, true, device);
}
@@ -228,13 +255,9 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
setCheckBoxColor(mCheckBox, mController.getColorItemContent());
}
- private void updateTitleIcon(@DrawableRes int id, int color) {
- mTitleIcon.setImageDrawable(mContext.getDrawable(id));
- mTitleIcon.setColorFilter(color);
- }
-
- private void updateContainerClickListener(View.OnClickListener listener) {
+ private void updateFullItemClickListener(View.OnClickListener listener) {
mContainerLayout.setOnClickListener(listener);
+ updateIconAreaClickListener(listener);
}
@Override
@@ -246,6 +269,11 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
final Drawable addDrawable = mContext.getDrawable(R.drawable.ic_add);
mTitleIcon.setImageDrawable(addDrawable);
mTitleIcon.setColorFilter(mController.getColorItemContent());
+ if (mController.isAdvancedLayoutSupported()) {
+ mIconAreaLayout.getBackground().setColorFilter(
+ new PorterDuffColorFilter(mController.getColorItemBackground(),
+ PorterDuff.Mode.SRC_IN));
+ }
mContainerLayout.setOnClickListener(mController::launchBluetoothPairing);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
index 3f7b2261ea52..db62e51be7e7 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
@@ -16,8 +16,11 @@
package com.android.systemui.media.dialog;
+import static com.android.systemui.media.dialog.MediaOutputSeekbar.VOLUME_PERCENTAGE_SCALE_SIZE;
+
import android.animation.Animator;
import android.animation.ValueAnimator;
+import android.annotation.DrawableRes;
import android.app.WallpaperColors;
import android.content.Context;
import android.graphics.PorterDuff;
@@ -80,8 +83,9 @@ public abstract class MediaOutputBaseAdapter extends
public MediaDeviceBaseViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup,
int viewType) {
mContext = viewGroup.getContext();
- mHolderView = LayoutInflater.from(mContext).inflate(R.layout.media_output_list_item,
- viewGroup, false);
+ mHolderView = LayoutInflater.from(mContext).inflate(
+ mController.isAdvancedLayoutSupported() ? R.layout.media_output_list_item_advanced
+ : R.layout.media_output_list_item, viewGroup, false);
return null;
}
@@ -129,18 +133,20 @@ public abstract class MediaOutputBaseAdapter extends
private static final int ANIM_DURATION = 500;
- final LinearLayout mContainerLayout;
+ final ViewGroup mContainerLayout;
final FrameLayout mItemLayout;
+ final FrameLayout mIconAreaLayout;
final TextView mTitleText;
final TextView mTwoLineTitleText;
final TextView mSubTitleText;
+ final TextView mVolumeValueText;
final ImageView mTitleIcon;
final ProgressBar mProgressBar;
final MediaOutputSeekbar mSeekBar;
final LinearLayout mTwoLineLayout;
final ImageView mStatusIcon;
final CheckBox mCheckBox;
- final LinearLayout mEndTouchArea;
+ final ViewGroup mEndTouchArea;
private String mDeviceId;
private ValueAnimator mCornerAnimator;
private ValueAnimator mVolumeAnimator;
@@ -159,6 +165,13 @@ public abstract class MediaOutputBaseAdapter extends
mStatusIcon = view.requireViewById(R.id.media_output_item_status);
mCheckBox = view.requireViewById(R.id.check_box);
mEndTouchArea = view.requireViewById(R.id.end_action_area);
+ if (mController.isAdvancedLayoutSupported()) {
+ mVolumeValueText = view.requireViewById(R.id.volume_value);
+ mIconAreaLayout = view.requireViewById(R.id.icon_area);
+ } else {
+ mVolumeValueText = null;
+ mIconAreaLayout = null;
+ }
initAnimator();
}
@@ -170,9 +183,14 @@ public abstract class MediaOutputBaseAdapter extends
mEndTouchArea.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
mContainerLayout.setOnClickListener(null);
mContainerLayout.setContentDescription(null);
+ mTitleIcon.setOnClickListener(null);
mTitleText.setTextColor(mController.getColorItemContent());
mSubTitleText.setTextColor(mController.getColorItemContent());
mTwoLineTitleText.setTextColor(mController.getColorItemContent());
+ if (mController.isAdvancedLayoutSupported()) {
+ mIconAreaLayout.setOnClickListener(null);
+ mVolumeValueText.setTextColor(mController.getColorItemContent());
+ }
mSeekBar.getProgressDrawable().setColorFilter(
new PorterDuffColorFilter(mController.getColorSeekbarProgress(),
PorterDuff.Mode.SRC_IN));
@@ -203,13 +221,28 @@ public abstract class MediaOutputBaseAdapter extends
.findDrawableByLayerId(android.R.id.progress);
final GradientDrawable progressDrawable =
(GradientDrawable) clipDrawable.getDrawable();
- progressDrawable.setCornerRadius(mController.getActiveRadius());
+ if (mController.isAdvancedLayoutSupported()) {
+ progressDrawable.setCornerRadii(
+ new float[]{0, 0, mController.getActiveRadius(),
+ mController.getActiveRadius(),
+ mController.getActiveRadius(),
+ mController.getActiveRadius(), 0, 0});
+ } else {
+ progressDrawable.setCornerRadius(mController.getActiveRadius());
+ }
}
}
mItemLayout.getBackground().setColorFilter(new PorterDuffColorFilter(
isActive ? mController.getColorConnectedItemBackground()
: mController.getColorItemBackground(),
PorterDuff.Mode.SRC_IN));
+ if (mController.isAdvancedLayoutSupported()) {
+ mIconAreaLayout.getBackground().setColorFilter(new PorterDuffColorFilter(
+ showSeekBar ? mController.getColorSeekbarProgress()
+ : showProgressBar ? mController.getColorConnectedItemBackground()
+ : mController.getColorItemBackground(),
+ PorterDuff.Mode.SRC_IN));
+ }
mProgressBar.setVisibility(showProgressBar ? View.VISIBLE : View.GONE);
mSeekBar.setAlpha(1);
mSeekBar.setVisibility(showSeekBar ? View.VISIBLE : View.GONE);
@@ -220,6 +253,13 @@ public abstract class MediaOutputBaseAdapter extends
mTitleText.setVisibility(View.VISIBLE);
mCheckBox.setVisibility(showCheckBox ? View.VISIBLE : View.GONE);
mEndTouchArea.setVisibility(showEndTouchArea ? View.VISIBLE : View.GONE);
+ if (mController.isAdvancedLayoutSupported()) {
+ ViewGroup.MarginLayoutParams params =
+ (ViewGroup.MarginLayoutParams) mItemLayout.getLayoutParams();
+ params.rightMargin = showEndTouchArea ? mController.getItemMarginEndSelectable()
+ : mController.getItemMarginEndDefault();
+ }
+ mTitleIcon.setColorFilter(mController.getColorItemContent());
}
void setTwoLineLayout(MediaDevice device, boolean bFocused, boolean showSeekBar,
@@ -263,42 +303,134 @@ public abstract class MediaOutputBaseAdapter extends
final int currentVolume = device.getCurrentVolume();
if (mSeekBar.getVolume() != currentVolume) {
if (isCurrentSeekbarInvisible && !mIsInitVolumeFirstTime) {
+ if (mController.isAdvancedLayoutSupported()) {
+ updateTitleIcon(currentVolume == 0 ? R.drawable.media_output_icon_volume_off
+ : R.drawable.media_output_icon_volume,
+ mController.getColorItemContent());
+ }
animateCornerAndVolume(mSeekBar.getProgress(),
MediaOutputSeekbar.scaleVolumeToProgress(currentVolume));
} else {
if (!mVolumeAnimator.isStarted()) {
+ if (mController.isAdvancedLayoutSupported()) {
+ int percentage =
+ (int) ((double) currentVolume * VOLUME_PERCENTAGE_SCALE_SIZE
+ / (double) mSeekBar.getMax());
+ if (percentage == 0) {
+ updateMutedVolumeIcon();
+ } else {
+ updateUnmutedVolumeIcon();
+ }
+ }
mSeekBar.setVolume(currentVolume);
}
}
+ } else if (mController.isAdvancedLayoutSupported() && currentVolume == 0) {
+ mSeekBar.resetVolume();
+ updateMutedVolumeIcon();
}
if (mIsInitVolumeFirstTime) {
mIsInitVolumeFirstTime = false;
}
+ if (mController.isAdvancedLayoutSupported()) {
+ updateIconAreaClickListener((v) -> {
+ mSeekBar.resetVolume();
+ mController.adjustVolume(device, 0);
+ updateMutedVolumeIcon();
+ });
+ }
mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
if (device == null || !fromUser) {
return;
}
- int currentVolume = MediaOutputSeekbar.scaleProgressToVolume(progress);
+ int progressToVolume = MediaOutputSeekbar.scaleProgressToVolume(progress);
int deviceVolume = device.getCurrentVolume();
- if (currentVolume != deviceVolume) {
- mController.adjustVolume(device, currentVolume);
+ if (mController.isAdvancedLayoutSupported()) {
+ int percentage =
+ (int) ((double) progressToVolume * VOLUME_PERCENTAGE_SCALE_SIZE
+ / (double) seekBar.getMax());
+ mVolumeValueText.setText(mContext.getResources().getString(
+ R.string.media_output_dialog_volume_percentage, percentage));
+ mVolumeValueText.setVisibility(View.VISIBLE);
+ }
+ if (progressToVolume != deviceVolume) {
+ mController.adjustVolume(device, progressToVolume);
+ if (mController.isAdvancedLayoutSupported() && deviceVolume == 0) {
+ updateUnmutedVolumeIcon();
+ }
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
+ if (mController.isAdvancedLayoutSupported()) {
+ mTitleIcon.setVisibility(View.INVISIBLE);
+ mVolumeValueText.setVisibility(View.VISIBLE);
+ }
mIsDragging = true;
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
+ if (mController.isAdvancedLayoutSupported()) {
+ int currentVolume = MediaOutputSeekbar.scaleProgressToVolume(
+ seekBar.getProgress());
+ int percentage =
+ (int) ((double) currentVolume * VOLUME_PERCENTAGE_SCALE_SIZE
+ / (double) seekBar.getMax());
+ if (percentage == 0) {
+ seekBar.setProgress(0);
+ updateMutedVolumeIcon();
+ } else {
+ updateUnmutedVolumeIcon();
+ }
+ mTitleIcon.setVisibility(View.VISIBLE);
+ mVolumeValueText.setVisibility(View.GONE);
+ }
mIsDragging = false;
}
});
}
+ void updateMutedVolumeIcon() {
+ updateTitleIcon(R.drawable.media_output_icon_volume_off,
+ mController.getColorItemContent());
+ final GradientDrawable iconAreaBackgroundDrawable =
+ (GradientDrawable) mIconAreaLayout.getBackground();
+ iconAreaBackgroundDrawable.setCornerRadius(mController.getActiveRadius());
+ }
+
+ void updateUnmutedVolumeIcon() {
+ updateTitleIcon(R.drawable.media_output_icon_volume,
+ mController.getColorItemContent());
+ final GradientDrawable iconAreaBackgroundDrawable =
+ (GradientDrawable) mIconAreaLayout.getBackground();
+ iconAreaBackgroundDrawable.setCornerRadii(new float[]{
+ mController.getActiveRadius(),
+ mController.getActiveRadius(),
+ 0, 0, 0, 0, mController.getActiveRadius(), mController.getActiveRadius()
+ });
+ }
+
+ void updateTitleIcon(@DrawableRes int id, int color) {
+ mTitleIcon.setImageDrawable(mContext.getDrawable(id));
+ mTitleIcon.setColorFilter(color);
+ if (mController.isAdvancedLayoutSupported()) {
+ mIconAreaLayout.getBackground().setColorFilter(
+ new PorterDuffColorFilter(mController.getColorSeekbarProgress(),
+ PorterDuff.Mode.SRC_IN));
+ }
+ }
+
+ void updateIconAreaClickListener(View.OnClickListener listener) {
+ if (mController.isAdvancedLayoutSupported()) {
+ mIconAreaLayout.setOnClickListener(listener);
+ }
+ mTitleIcon.setOnClickListener(listener);
+ }
+
void initMutingExpectedDevice() {
disableSeekBar();
final Drawable backgroundDrawable = mContext.getDrawable(
@@ -316,11 +448,26 @@ public abstract class MediaOutputBaseAdapter extends
final ClipDrawable clipDrawable =
(ClipDrawable) ((LayerDrawable) mSeekBar.getProgressDrawable())
.findDrawableByLayerId(android.R.id.progress);
- final GradientDrawable progressDrawable = (GradientDrawable) clipDrawable.getDrawable();
+ final GradientDrawable targetBackgroundDrawable =
+ (GradientDrawable) (mController.isAdvancedLayoutSupported()
+ ? mIconAreaLayout.getBackground()
+ : clipDrawable.getDrawable());
mCornerAnimator.addUpdateListener(animation -> {
float value = (float) animation.getAnimatedValue();
layoutBackgroundDrawable.setCornerRadius(value);
- progressDrawable.setCornerRadius(value);
+ if (mController.isAdvancedLayoutSupported()) {
+ if (toProgress == 0) {
+ targetBackgroundDrawable.setCornerRadius(value);
+ } else {
+ targetBackgroundDrawable.setCornerRadii(new float[]{
+ value,
+ value,
+ 0, 0, 0, 0, value, value
+ });
+ }
+ } else {
+ targetBackgroundDrawable.setCornerRadius(value);
+ }
});
mVolumeAnimator.setIntValues(fromProgress, toProgress);
mVolumeAnimator.start();
@@ -391,6 +538,7 @@ public abstract class MediaOutputBaseAdapter extends
return;
}
mTitleIcon.setImageIcon(icon);
+ mTitleIcon.setColorFilter(mController.getColorItemContent());
});
});
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt
index 2b5d6fd63995..cdd00f99fa29 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt
@@ -26,6 +26,7 @@ import com.android.internal.logging.UiEventLogger
import com.android.settingslib.bluetooth.LocalBluetoothManager
import com.android.systemui.animation.DialogLaunchAnimator
import com.android.systemui.broadcast.BroadcastSender
+import com.android.systemui.flags.FeatureFlags
import com.android.systemui.media.nearby.NearbyMediaDevicesManager
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
@@ -47,7 +48,8 @@ class MediaOutputBroadcastDialogFactory @Inject constructor(
private val nearbyMediaDevicesManagerOptional: Optional<NearbyMediaDevicesManager>,
private val audioManager: AudioManager,
private val powerExemptionManager: PowerExemptionManager,
- private val keyGuardManager: KeyguardManager
+ private val keyGuardManager: KeyguardManager,
+ private val featureFlags: FeatureFlags
) {
var mediaOutputBroadcastDialog: MediaOutputBroadcastDialog? = null
@@ -59,7 +61,7 @@ class MediaOutputBroadcastDialogFactory @Inject constructor(
val controller = MediaOutputController(context, packageName,
mediaSessionManager, lbm, starter, notifCollection,
dialogLaunchAnimator, nearbyMediaDevicesManagerOptional, audioManager,
- powerExemptionManager, keyGuardManager)
+ powerExemptionManager, keyGuardManager, featureFlags)
val dialog =
MediaOutputBroadcastDialog(context, aboveStatusBar, broadcastSender, controller)
mediaOutputBroadcastDialog = dialog
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
index 19b401d80600..9b361e3edc57 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -73,6 +73,8 @@ import com.android.systemui.R;
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.broadcast.BroadcastSender;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.media.nearby.NearbyMediaDevicesManager;
import com.android.systemui.monet.ColorScheme;
import com.android.systemui.plugins.ActivityStarter;
@@ -145,8 +147,11 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback,
private int mColorConnectedItemBackground;
private int mColorPositiveButtonText;
private int mColorDialogBackground;
+ private int mItemMarginEndDefault;
+ private int mItemMarginEndSelectable;
private float mInactiveRadius;
private float mActiveRadius;
+ private FeatureFlags mFeatureFlags;
public enum BroadcastNotifyDialog {
ACTION_FIRST_LAUNCH,
@@ -162,7 +167,8 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback,
Optional<NearbyMediaDevicesManager> nearbyMediaDevicesManagerOptional,
AudioManager audioManager,
PowerExemptionManager powerExemptionManager,
- KeyguardManager keyGuardManager) {
+ KeyguardManager keyGuardManager,
+ FeatureFlags featureFlags) {
mContext = context;
mPackageName = packageName;
mMediaSessionManager = mediaSessionManager;
@@ -172,6 +178,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback,
mAudioManager = audioManager;
mPowerExemptionManager = powerExemptionManager;
mKeyGuardManager = keyGuardManager;
+ mFeatureFlags = featureFlags;
InfoMediaManager imm = new InfoMediaManager(mContext, packageName, null, lbm);
mLocalMediaManager = new LocalMediaManager(mContext, lbm, imm, packageName);
mMetricLogger = new MediaOutputMetricLogger(mContext, mPackageName);
@@ -195,6 +202,10 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback,
R.dimen.media_output_dialog_active_background_radius);
mColorDialogBackground = Utils.getColorStateListDefaultColor(mContext,
R.color.media_dialog_background);
+ mItemMarginEndDefault = (int) mContext.getResources().getDimension(
+ R.dimen.media_output_dialog_default_margin_end);
+ mItemMarginEndSelectable = (int) mContext.getResources().getDimension(
+ R.dimen.media_output_dialog_selectable_margin_end);
}
void start(@NonNull Callback cb) {
@@ -527,6 +538,14 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback,
return mActiveRadius;
}
+ public int getItemMarginEndDefault() {
+ return mItemMarginEndDefault;
+ }
+
+ public int getItemMarginEndSelectable() {
+ return mItemMarginEndSelectable;
+ }
+
private void buildMediaDevices(List<MediaDevice> devices) {
synchronized (mMediaDevicesLock) {
attachRangeInfo(devices);
@@ -599,6 +618,10 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback,
currentConnectedMediaDevice);
}
+ public boolean isAdvancedLayoutSupported() {
+ return mFeatureFlags.isEnabled(Flags.OUTPUT_SWITCHER_ADVANCED_LAYOUT);
+ }
+
List<MediaDevice> getGroupMediaDevices() {
final List<MediaDevice> selectedDevices = getSelectedMediaDevice();
final List<MediaDevice> selectableDevices = getSelectableMediaDevice();
@@ -792,7 +815,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback,
MediaOutputController controller = new MediaOutputController(mContext, mPackageName,
mMediaSessionManager, mLocalBluetoothManager, mActivityStarter,
mNotifCollection, mDialogLaunchAnimator, Optional.of(mNearbyMediaDevicesManager),
- mAudioManager, mPowerExemptionManager, mKeyGuardManager);
+ mAudioManager, mPowerExemptionManager, mKeyGuardManager, mFeatureFlags);
MediaOutputBroadcastDialog dialog = new MediaOutputBroadcastDialog(mContext, true,
broadcastSender, controller);
mDialogLaunchAnimator.showFromView(dialog, mediaOutputDialog);
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
index 543efed8378e..7dbf876bb377 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
@@ -31,6 +31,7 @@ import com.android.systemui.broadcast.BroadcastSender
import com.android.systemui.media.nearby.NearbyMediaDevicesManager
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
+import com.android.systemui.flags.FeatureFlags
import java.util.Optional
import javax.inject.Inject
@@ -49,7 +50,8 @@ class MediaOutputDialogFactory @Inject constructor(
private val nearbyMediaDevicesManagerOptional: Optional<NearbyMediaDevicesManager>,
private val audioManager: AudioManager,
private val powerExemptionManager: PowerExemptionManager,
- private val keyGuardManager: KeyguardManager
+ private val keyGuardManager: KeyguardManager,
+ private val featureFlags: FeatureFlags
) {
companion object {
private const val INTERACTION_JANK_TAG = "media_output"
@@ -65,7 +67,7 @@ class MediaOutputDialogFactory @Inject constructor(
context, packageName,
mediaSessionManager, lbm, starter, notifCollection,
dialogLaunchAnimator, nearbyMediaDevicesManagerOptional, audioManager,
- powerExemptionManager, keyGuardManager)
+ powerExemptionManager, keyGuardManager, featureFlags)
val dialog =
MediaOutputDialog(context, aboveStatusBar, broadcastSender, controller, uiEventLogger)
mediaOutputDialog = dialog
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSeekbar.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSeekbar.java
index 4ff79d689037..253c3c713485 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSeekbar.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSeekbar.java
@@ -26,6 +26,7 @@ import android.widget.SeekBar;
*/
public class MediaOutputSeekbar extends SeekBar {
private static final int SCALE_SIZE = 1000;
+ public static final int VOLUME_PERCENTAGE_SCALE_SIZE = 100000;
public MediaOutputSeekbar(Context context, AttributeSet attrs) {
super(context, attrs);
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
index 647beb95a3bc..b10abb569717 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
@@ -48,52 +48,66 @@ class MediaTttCommandLineHelper @Inject constructor(
/** All commands for the sender device. */
inner class SenderCommand : Command {
override fun execute(pw: PrintWriter, args: List<String>) {
- val commandName = args[1]
+ if (args.size < 2) {
+ help(pw)
+ return
+ }
+
+ val senderArgs = processArgs(args)
+
@StatusBarManager.MediaTransferSenderState
val displayState: Int?
try {
- displayState = ChipStateSender.getSenderStateIdFromName(commandName)
+ displayState = ChipStateSender.getSenderStateIdFromName(senderArgs.commandName)
} catch (ex: IllegalArgumentException) {
- pw.println("Invalid command name $commandName")
+ pw.println("Invalid command name ${senderArgs.commandName}")
return
}
@SuppressLint("WrongConstant") // sysui allowed to call STATUS_BAR_SERVICE
val statusBarManager = context.getSystemService(Context.STATUS_BAR_SERVICE)
as StatusBarManager
- val routeInfo = MediaRoute2Info.Builder(if (args.size >= 4) args[3] else "id", args[0])
+ val routeInfo = MediaRoute2Info.Builder(senderArgs.id, senderArgs.deviceName)
.addFeature("feature")
- val useAppIcon = !(args.size >= 3 && args[2] == "useAppIcon=false")
- if (useAppIcon) {
+ if (senderArgs.useAppIcon) {
routeInfo.setClientPackageName(TEST_PACKAGE_NAME)
}
+ var undoExecutor: Executor? = null
+ var undoRunnable: Runnable? = null
+ if (isSucceededState(displayState) && senderArgs.showUndo) {
+ undoExecutor = mainExecutor
+ undoRunnable = Runnable { Log.i(CLI_TAG, "Undo triggered for $displayState") }
+ }
+
statusBarManager.updateMediaTapToTransferSenderDisplay(
displayState,
routeInfo.build(),
- getUndoExecutor(displayState),
- getUndoCallback(displayState)
+ undoExecutor,
+ undoRunnable,
)
}
- private fun getUndoExecutor(
- @StatusBarManager.MediaTransferSenderState displayState: Int
- ): Executor? {
- return if (isSucceededState(displayState)) {
- mainExecutor
- } else {
- null
+ private fun processArgs(args: List<String>): SenderArgs {
+ val senderArgs = SenderArgs(
+ deviceName = args[0],
+ commandName = args[1],
+ )
+
+ if (args.size == 2) {
+ return senderArgs
}
- }
- private fun getUndoCallback(
- @StatusBarManager.MediaTransferSenderState displayState: Int
- ): Runnable? {
- return if (isSucceededState(displayState)) {
- Runnable { Log.i(CLI_TAG, "Undo triggered for $displayState") }
- } else {
- null
+ // Process any optional arguments
+ args.subList(2, args.size).forEach {
+ when {
+ it == "useAppIcon=false" -> senderArgs.useAppIcon = false
+ it == "showUndo=false" -> senderArgs.showUndo = false
+ it.substring(0, 3) == "id=" -> senderArgs.id = it.substring(3)
+ }
}
+
+ return senderArgs
}
private fun isSucceededState(
@@ -106,14 +120,31 @@ class MediaTttCommandLineHelper @Inject constructor(
}
override fun help(pw: PrintWriter) {
- pw.println("Usage: adb shell cmd statusbar $SENDER_COMMAND " +
- "<deviceName> <chipState> useAppIcon=[true|false] <id>")
+ pw.println(
+ "Usage: adb shell cmd statusbar $SENDER_COMMAND " +
+ "<deviceName> <chipState> " +
+ "useAppIcon=[true|false] id=<id> showUndo=[true|false]"
+ )
+ pw.println("Note: useAppIcon, id, and showUndo are optional additional commands.")
}
}
+ private data class SenderArgs(
+ val deviceName: String,
+ val commandName: String,
+ var id: String = "id",
+ var useAppIcon: Boolean = true,
+ var showUndo: Boolean = true,
+ )
+
/** All commands for the receiver device. */
inner class ReceiverCommand : Command {
override fun execute(pw: PrintWriter, args: List<String>) {
+ if (args.isEmpty()) {
+ help(pw)
+ return
+ }
+
val commandName = args[0]
@StatusBarManager.MediaTransferReceiverState
val displayState: Int?
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt
index 120f7d673881..b55bedda2dc1 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt
@@ -58,6 +58,27 @@ class MediaTttLogger(
)
}
+ /**
+ * Logs an invalid sender state transition error in trying to update to [desiredState].
+ *
+ * @param currentState the previous state of the chip.
+ * @param desiredState the new state of the chip.
+ */
+ fun logInvalidStateTransitionError(
+ currentState: String,
+ desiredState: String
+ ) {
+ buffer.log(
+ tag,
+ LogLevel.ERROR,
+ {
+ str1 = currentState
+ str2 = desiredState
+ },
+ { "Cannot display state=$str2 after state=$str1; invalid transition" }
+ )
+ }
+
/** Logs that we couldn't find information for [packageName]. */
fun logPackageNotFound(packageName: String) {
buffer.log(
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt
index 769494a58842..009595a6da8b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt
@@ -19,10 +19,12 @@ package com.android.systemui.media.taptotransfer.common
import android.content.Context
import android.content.pm.PackageManager
import android.graphics.drawable.Drawable
-import com.android.settingslib.Utils
+import androidx.annotation.AttrRes
+import androidx.annotation.DrawableRes
import com.android.systemui.R
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.common.shared.model.TintedIcon
/** Utility methods for media tap-to-transfer. */
class MediaTttUtils {
@@ -34,23 +36,6 @@ class MediaTttUtils {
const val WAKE_REASON_RECEIVER = "MEDIA_TRANSFER_ACTIVATED_RECEIVER"
/**
- * Returns the information needed to display the icon in [Icon] form.
- *
- * See [getIconInfoFromPackageName].
- */
- fun getIconFromPackageName(
- context: Context,
- appPackageName: String?,
- logger: MediaTttLogger,
- ): Icon {
- val iconInfo = getIconInfoFromPackageName(context, appPackageName, logger)
- return Icon.Loaded(
- iconInfo.drawable,
- ContentDescription.Loaded(iconInfo.contentDescription)
- )
- }
-
- /**
* Returns the information needed to display the icon.
*
* The information will either contain app name and icon of the app playing media, or a
@@ -65,18 +50,22 @@ class MediaTttUtils {
logger: MediaTttLogger
): IconInfo {
if (appPackageName != null) {
+ val packageManager = context.packageManager
try {
val contentDescription =
- context.packageManager
- .getApplicationInfo(
- appPackageName,
- PackageManager.ApplicationInfoFlags.of(0)
- )
- .loadLabel(context.packageManager)
- .toString()
+ ContentDescription.Loaded(
+ packageManager
+ .getApplicationInfo(
+ appPackageName,
+ PackageManager.ApplicationInfoFlags.of(0)
+ )
+ .loadLabel(packageManager)
+ .toString()
+ )
return IconInfo(
contentDescription,
- drawable = context.packageManager.getApplicationIcon(appPackageName),
+ MediaTttIcon.Loaded(packageManager.getApplicationIcon(appPackageName)),
+ tintAttr = null,
isAppIcon = true
)
} catch (e: PackageManager.NameNotFoundException) {
@@ -84,25 +73,41 @@ class MediaTttUtils {
}
}
return IconInfo(
- contentDescription =
- context.getString(R.string.media_output_dialog_unknown_launch_app_name),
- drawable =
- context.resources.getDrawable(R.drawable.ic_cast).apply {
- this.setTint(
- Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary)
- )
- },
+ ContentDescription.Resource(R.string.media_output_dialog_unknown_launch_app_name),
+ MediaTttIcon.Resource(R.drawable.ic_cast),
+ tintAttr = android.R.attr.textColorPrimary,
isAppIcon = false
)
}
}
}
+/** Stores all the information for an icon shown with media TTT. */
data class IconInfo(
- val contentDescription: String,
- val drawable: Drawable,
+ val contentDescription: ContentDescription,
+ val icon: MediaTttIcon,
+ @AttrRes val tintAttr: Int?,
/**
* True if [drawable] is the app's icon, and false if [drawable] is some generic default icon.
*/
val isAppIcon: Boolean
-)
+) {
+ /** Converts this into a [TintedIcon]. */
+ fun toTintedIcon(): TintedIcon {
+ val iconOutput =
+ when (icon) {
+ is MediaTttIcon.Loaded -> Icon.Loaded(icon.drawable, contentDescription)
+ is MediaTttIcon.Resource -> Icon.Resource(icon.res, contentDescription)
+ }
+ return TintedIcon(iconOutput, tintAttr)
+ }
+}
+
+/**
+ * Mimics [com.android.systemui.common.shared.model.Icon] but without the content description, since
+ * the content description may need to be overridden.
+ */
+sealed interface MediaTttIcon {
+ data class Loaded(val drawable: Drawable) : MediaTttIcon
+ data class Resource(@DrawableRes val res: Int) : MediaTttIcon
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
index cc5e256c0956..1c3a53cbf815 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
@@ -33,9 +33,12 @@ import android.view.accessibility.AccessibilityManager
import com.android.internal.widget.CachingIconView
import com.android.settingslib.Utils
import com.android.systemui.R
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.ui.binder.TintedIconViewBinder
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.media.taptotransfer.MediaTttFlags
+import com.android.systemui.media.taptotransfer.common.MediaTttIcon
import com.android.systemui.media.taptotransfer.common.MediaTttLogger
import com.android.systemui.media.taptotransfer.common.MediaTttUtils
import com.android.systemui.statusbar.CommandQueue
@@ -161,11 +164,23 @@ open class MediaTttChipControllerReceiver @Inject constructor(
}
override fun updateView(newInfo: ChipReceiverInfo, currentView: ViewGroup) {
- val iconInfo = MediaTttUtils.getIconInfoFromPackageName(
+ var iconInfo = MediaTttUtils.getIconInfoFromPackageName(
context, newInfo.routeInfo.clientPackageName, logger
)
- val iconDrawable = newInfo.appIconDrawableOverride ?: iconInfo.drawable
- val iconContentDescription = newInfo.appNameOverride ?: iconInfo.contentDescription
+
+ if (newInfo.appNameOverride != null) {
+ iconInfo = iconInfo.copy(
+ contentDescription = ContentDescription.Loaded(newInfo.appNameOverride.toString())
+ )
+ }
+
+ if (newInfo.appIconDrawableOverride != null) {
+ iconInfo = iconInfo.copy(
+ icon = MediaTttIcon.Loaded(newInfo.appIconDrawableOverride),
+ isAppIcon = true,
+ )
+ }
+
val iconPadding =
if (iconInfo.isAppIcon) {
0
@@ -175,8 +190,7 @@ open class MediaTttChipControllerReceiver @Inject constructor(
val iconView = currentView.getAppIconView()
iconView.setPadding(iconPadding, iconPadding, iconPadding, iconPadding)
- iconView.setImageDrawable(iconDrawable)
- iconView.contentDescription = iconContentDescription
+ TintedIconViewBinder.bind(iconInfo.toTintedIcon(), iconView)
}
override fun animateViewIn(view: ViewGroup) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
index af7317c208ab..1f27582cb7aa 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
@@ -56,7 +56,12 @@ enum class ChipStateSender(
R.string.media_move_closer_to_start_cast,
transferStatus = TransferStatus.NOT_STARTED,
endItem = null,
- ),
+ ) {
+ override fun isValidNextState(nextState: ChipStateSender): Boolean {
+ return nextState == FAR_FROM_RECEIVER ||
+ nextState == TRANSFER_TO_RECEIVER_TRIGGERED
+ }
+ },
/**
* A state representing that the two devices are close but not close enough to *end* a cast
@@ -70,7 +75,12 @@ enum class ChipStateSender(
R.string.media_move_closer_to_end_cast,
transferStatus = TransferStatus.NOT_STARTED,
endItem = null,
- ),
+ ) {
+ override fun isValidNextState(nextState: ChipStateSender): Boolean {
+ return nextState == FAR_FROM_RECEIVER ||
+ nextState == TRANSFER_TO_THIS_DEVICE_TRIGGERED
+ }
+ },
/**
* A state representing that a transfer to the receiver device has been initiated (but not
@@ -83,7 +93,13 @@ enum class ChipStateSender(
transferStatus = TransferStatus.IN_PROGRESS,
endItem = SenderEndItem.Loading,
timeout = TRANSFER_TRIGGERED_TIMEOUT_MILLIS
- ),
+ ) {
+ override fun isValidNextState(nextState: ChipStateSender): Boolean {
+ return nextState == FAR_FROM_RECEIVER ||
+ nextState == TRANSFER_TO_RECEIVER_SUCCEEDED ||
+ nextState == TRANSFER_TO_RECEIVER_FAILED
+ }
+ },
/**
* A state representing that a transfer from the receiver device and back to this device (the
@@ -96,7 +112,13 @@ enum class ChipStateSender(
transferStatus = TransferStatus.IN_PROGRESS,
endItem = SenderEndItem.Loading,
timeout = TRANSFER_TRIGGERED_TIMEOUT_MILLIS
- ),
+ ) {
+ override fun isValidNextState(nextState: ChipStateSender): Boolean {
+ return nextState == FAR_FROM_RECEIVER ||
+ nextState == TRANSFER_TO_THIS_DEVICE_SUCCEEDED ||
+ nextState == TRANSFER_TO_THIS_DEVICE_FAILED
+ }
+ },
/**
* A state representing that a transfer to the receiver device has been successfully completed.
@@ -112,7 +134,13 @@ enum class ChipStateSender(
newState =
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED
),
- ),
+ ) {
+ override fun isValidNextState(nextState: ChipStateSender): Boolean {
+ return nextState == FAR_FROM_RECEIVER ||
+ nextState == ALMOST_CLOSE_TO_START_CAST ||
+ nextState == TRANSFER_TO_THIS_DEVICE_TRIGGERED
+ }
+ },
/**
* A state representing that a transfer back to this device has been successfully completed.
@@ -128,7 +156,13 @@ enum class ChipStateSender(
newState =
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED
),
- ),
+ ) {
+ override fun isValidNextState(nextState: ChipStateSender): Boolean {
+ return nextState == FAR_FROM_RECEIVER ||
+ nextState == ALMOST_CLOSE_TO_END_CAST ||
+ nextState == TRANSFER_TO_RECEIVER_TRIGGERED
+ }
+ },
/** A state representing that a transfer to the receiver device has failed. */
TRANSFER_TO_RECEIVER_FAILED(
@@ -137,7 +171,13 @@ enum class ChipStateSender(
R.string.media_transfer_failed,
transferStatus = TransferStatus.FAILED,
endItem = SenderEndItem.Error,
- ),
+ ) {
+ override fun isValidNextState(nextState: ChipStateSender): Boolean {
+ return nextState == FAR_FROM_RECEIVER ||
+ nextState == ALMOST_CLOSE_TO_START_CAST ||
+ nextState == TRANSFER_TO_THIS_DEVICE_TRIGGERED
+ }
+ },
/** A state representing that a transfer back to this device has failed. */
TRANSFER_TO_THIS_DEVICE_FAILED(
@@ -146,7 +186,13 @@ enum class ChipStateSender(
R.string.media_transfer_failed,
transferStatus = TransferStatus.FAILED,
endItem = SenderEndItem.Error,
- ),
+ ) {
+ override fun isValidNextState(nextState: ChipStateSender): Boolean {
+ return nextState == FAR_FROM_RECEIVER ||
+ nextState == ALMOST_CLOSE_TO_END_CAST ||
+ nextState == TRANSFER_TO_RECEIVER_TRIGGERED
+ }
+ },
/** A state representing that this device is far away from any receiver device. */
FAR_FROM_RECEIVER(
@@ -162,6 +208,12 @@ enum class ChipStateSender(
throw IllegalArgumentException("FAR_FROM_RECEIVER should never be displayed, " +
"so its string should never be fetched")
}
+
+ override fun isValidNextState(nextState: ChipStateSender): Boolean {
+ return nextState == FAR_FROM_RECEIVER ||
+ nextState.transferStatus == TransferStatus.NOT_STARTED ||
+ nextState.transferStatus == TransferStatus.IN_PROGRESS
+ }
};
/**
@@ -175,6 +227,8 @@ enum class ChipStateSender(
return Text.Loaded(context.getString(stringResId!!, otherDeviceName))
}
+ abstract fun isValidNextState(nextState: ChipStateSender): Boolean
+
companion object {
/**
* Returns the sender state enum associated with the given [displayState] from
@@ -197,6 +251,31 @@ enum class ChipStateSender(
*/
@StatusBarManager.MediaTransferSenderState
fun getSenderStateIdFromName(name: String): Int = valueOf(name).stateInt
+
+ /**
+ * Validates the transition from a chip state to another.
+ *
+ * @param currentState is the current state of the chip.
+ * @param desiredState is the desired state of the chip.
+ * @return true if the transition from [currentState] to [desiredState] is valid, and false
+ * otherwise.
+ */
+ fun isValidStateTransition(
+ currentState: ChipStateSender?,
+ desiredState: ChipStateSender,
+ ): Boolean {
+ // Far from receiver is the default state.
+ if (currentState == null) {
+ return FAR_FROM_RECEIVER.isValidNextState(desiredState)
+ }
+
+ // No change in state is valid.
+ if (currentState == desiredState) {
+ return true
+ }
+
+ return currentState.isValidNextState(desiredState)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
index bb7bc6fff99f..ec1984d78cf9 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
@@ -52,6 +52,8 @@ constructor(
) : CoreStartable {
private var displayedState: ChipStateSender? = null
+ // A map to store current chip state per id.
+ private var stateMap: MutableMap<String, ChipStateSender> = mutableMapOf()
private val commandQueueCallbacks =
object : CommandQueue.Callbacks {
@@ -87,9 +89,22 @@ constructor(
logger.logStateChangeError(displayState)
return
}
+
+ val currentState = stateMap[routeInfo.id]
+ if (!ChipStateSender.isValidStateTransition(currentState, chipState)) {
+ // ChipStateSender.FAR_FROM_RECEIVER is the default state when there is no state.
+ logger.logInvalidStateTransitionError(
+ currentState = currentState?.name ?: ChipStateSender.FAR_FROM_RECEIVER.name,
+ chipState.name
+ )
+ return
+ }
uiEventLogger.logSenderStateChange(chipState)
+ stateMap.put(routeInfo.id, chipState)
if (chipState == ChipStateSender.FAR_FROM_RECEIVER) {
+ // No need to store the state since it is the default state
+ stateMap.remove(routeInfo.id)
// Return early if we're not displaying a chip anyway
val currentDisplayedState = displayedState ?: return
@@ -119,7 +134,7 @@ constructor(
context,
logger,
)
- )
+ ) { stateMap.remove(routeInfo.id) }
}
}
@@ -138,7 +153,9 @@ constructor(
return ChipbarInfo(
// Display the app's icon as the start icon
- startIcon = MediaTttUtils.getIconFromPackageName(context, packageName, logger),
+ startIcon =
+ MediaTttUtils.getIconInfoFromPackageName(context, packageName, logger)
+ .toTintedIcon(),
text = chipStateSender.getChipTextString(context, otherDeviceName),
endItem =
when (chipStateSender.endItem) {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
index 3fd1aa73c033..9791e82e5a3f 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
@@ -98,7 +98,7 @@ public class NavigationBarController implements
// Tracks config changes that will actually recreate the nav bar
private final InterestingConfigChanges mConfigChanges = new InterestingConfigChanges(
- ActivityInfo.CONFIG_FONT_SCALE | ActivityInfo.CONFIG_SCREEN_LAYOUT
+ ActivityInfo.CONFIG_FONT_SCALE
| ActivityInfo.CONFIG_UI_MODE);
@Inject
@@ -145,7 +145,7 @@ public class NavigationBarController implements
boolean willApplyConfig = mConfigChanges.applyNewConfig(mContext.getResources());
boolean largeScreenChanged = mIsTablet != isOldConfigTablet;
// TODO(b/243765256): Disable this logging once b/243765256 is fixed.
- Log.d(DEBUG_MISSING_GESTURE_TAG, "NavbarController: newConfig=" + newConfig
+ Log.i(DEBUG_MISSING_GESTURE_TAG, "NavbarController: newConfig=" + newConfig
+ " mTaskbarDelegate initialized=" + mTaskbarDelegate.isInitialized()
+ " willApplyConfigToNavbars=" + willApplyConfig
+ " navBarCount=" + mNavigationBars.size());
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 26d39028e176..f97385bf57e3 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -977,7 +977,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
}
// TODO(b/243765256): Disable this logging once b/243765256 is fixed.
- Log.d(DEBUG_MISSING_GESTURE_TAG, "Config changed: newConfig=" + newConfig
+ Log.i(DEBUG_MISSING_GESTURE_TAG, "Config changed: newConfig=" + newConfig
+ " lastReportedConfig=" + mLastReportedConfig);
mLastReportedConfig.updateFrom(newConfig);
updateDisplaySize();
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
index b964b76795b8..6dd60d043a06 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
@@ -17,10 +17,12 @@
package com.android.systemui.notetask
import android.app.KeyguardManager
+import android.content.ComponentName
import android.content.Context
+import android.content.pm.PackageManager
import android.os.UserManager
-import android.view.KeyEvent
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity
import com.android.systemui.util.kotlin.getOrNull
import com.android.wm.shell.bubbles.Bubbles
import java.util.Optional
@@ -45,15 +47,22 @@ constructor(
@NoteTaskEnabledKey private val isEnabled: Boolean,
) {
- fun handleSystemKey(keyCode: Int) {
+ /**
+ * Shows a note task. How the task is shown will depend on when the method is invoked.
+ *
+ * If in multi-window mode, notes will open as a full screen experience. That is particularly
+ * important for Large screen devices. These devices may support a taskbar that let users to
+ * drag and drop a shortcut into multi-window mode, and notes should comply with this behaviour.
+ *
+ * If the keyguard is locked, notes will open as a full screen experience. A locked device has
+ * no contextual information which let us use the whole screen space available.
+ *
+ * If no in multi-window or the keyguard is unlocked, notes will open as a floating experience.
+ * That will let users open other apps in full screen, and take contextual notes.
+ */
+ fun showNoteTask(isInMultiWindowMode: Boolean = false) {
if (!isEnabled) return
- if (keyCode == KeyEvent.KEYCODE_VIDEO_APP_1) {
- showNoteTask()
- }
- }
-
- private fun showNoteTask() {
val bubbles = optionalBubbles.getOrNull() ?: return
val keyguardManager = optionalKeyguardManager.getOrNull() ?: return
val userManager = optionalUserManager.getOrNull() ?: return
@@ -62,11 +71,35 @@ constructor(
// TODO(b/249954038): We should handle direct boot (isUserUnlocked). For now, we do nothing.
if (!userManager.isUserUnlocked) return
- if (keyguardManager.isKeyguardLocked) {
+ if (isInMultiWindowMode || keyguardManager.isKeyguardLocked) {
context.startActivity(intent)
} else {
// TODO(b/254606432): Should include Intent.EXTRA_FLOATING_WINDOW_MODE parameter.
bubbles.showAppBubble(intent)
}
}
+
+ /**
+ * Set `android:enabled` property in the `AndroidManifest` associated with the Shortcut
+ * component to [value].
+ *
+ * If the shortcut entry `android:enabled` is set to `true`, the shortcut will be visible in the
+ * Widget Picker to all users.
+ */
+ fun setNoteTaskShortcutEnabled(value: Boolean) {
+ val componentName = ComponentName(context, CreateNoteTaskShortcutActivity::class.java)
+
+ val enabledState =
+ if (value) {
+ PackageManager.COMPONENT_ENABLED_STATE_ENABLED
+ } else {
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED
+ }
+
+ context.packageManager.setComponentEnabledSetting(
+ componentName,
+ enabledState,
+ PackageManager.DONT_KILL_APP,
+ )
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
index 0a5b6008981b..d14b7a766762 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
@@ -16,9 +16,10 @@
package com.android.systemui.notetask
+import android.view.KeyEvent
+import androidx.annotation.VisibleForTesting
import com.android.systemui.statusbar.CommandQueue
import com.android.wm.shell.bubbles.Bubbles
-import dagger.Lazy
import java.util.Optional
import javax.inject.Inject
@@ -27,15 +28,18 @@ internal class NoteTaskInitializer
@Inject
constructor(
private val optionalBubbles: Optional<Bubbles>,
- private val lazyNoteTaskController: Lazy<NoteTaskController>,
+ private val noteTaskController: NoteTaskController,
private val commandQueue: CommandQueue,
@NoteTaskEnabledKey private val isEnabled: Boolean,
) {
- private val callbacks =
+ @VisibleForTesting
+ val callbacks =
object : CommandQueue.Callbacks {
override fun handleSystemKey(keyCode: Int) {
- lazyNoteTaskController.get().handleSystemKey(keyCode)
+ if (keyCode == KeyEvent.KEYCODE_VIDEO_APP_1) {
+ noteTaskController.showNoteTask()
+ }
}
}
@@ -43,5 +47,6 @@ constructor(
if (isEnabled && optionalBubbles.isPresent) {
commandQueue.addCallback(callbacks)
}
+ noteTaskController.setNoteTaskShortcutEnabled(isEnabled)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
index 035396a6fc76..8bdf3195d53b 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
@@ -16,32 +16,47 @@
package com.android.systemui.notetask
+import android.app.Activity
import android.app.KeyguardManager
import android.content.Context
import android.os.UserManager
import androidx.core.content.getSystemService
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
+import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity
+import com.android.systemui.notetask.shortcut.LaunchNoteTaskActivity
+import dagger.Binds
import dagger.Module
import dagger.Provides
-import java.util.*
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+import java.util.Optional
/** Compose all dependencies required by Note Task feature. */
@Module
-internal class NoteTaskModule {
+internal interface NoteTaskModule {
- @[Provides NoteTaskEnabledKey]
- fun provideIsNoteTaskEnabled(featureFlags: FeatureFlags): Boolean {
- return featureFlags.isEnabled(Flags.NOTE_TASKS)
- }
+ @[Binds IntoMap ClassKey(LaunchNoteTaskActivity::class)]
+ fun bindNoteTaskLauncherActivity(activity: LaunchNoteTaskActivity): Activity?
- @Provides
- fun provideOptionalKeyguardManager(context: Context): Optional<KeyguardManager> {
- return Optional.ofNullable(context.getSystemService())
- }
+ @[Binds IntoMap ClassKey(CreateNoteTaskShortcutActivity::class)]
+ fun bindNoteTaskShortcutActivity(activity: CreateNoteTaskShortcutActivity): Activity?
+
+ companion object {
+
+ @[Provides NoteTaskEnabledKey]
+ fun provideIsNoteTaskEnabled(featureFlags: FeatureFlags): Boolean {
+ return featureFlags.isEnabled(Flags.NOTE_TASKS)
+ }
+
+ @Provides
+ fun provideOptionalKeyguardManager(context: Context): Optional<KeyguardManager> {
+ return Optional.ofNullable(context.getSystemService())
+ }
- @Provides
- fun provideOptionalUserManager(context: Context): Optional<UserManager> {
- return Optional.ofNullable(context.getSystemService())
+ @Provides
+ fun provideOptionalUserManager(context: Context): Optional<UserManager> {
+ return Optional.ofNullable(context.getSystemService())
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/shortcut/CreateNoteTaskShortcutActivity.kt b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/CreateNoteTaskShortcutActivity.kt
new file mode 100644
index 000000000000..f6a623e4f001
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/CreateNoteTaskShortcutActivity.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.notetask.shortcut
+
+import android.app.Activity
+import android.content.Intent
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.annotation.DrawableRes
+import androidx.core.content.pm.ShortcutInfoCompat
+import androidx.core.content.pm.ShortcutManagerCompat
+import androidx.core.graphics.drawable.IconCompat
+import com.android.systemui.R
+import javax.inject.Inject
+
+/**
+ * Activity responsible for create a shortcut for notes action. If the shortcut is enabled, a new
+ * shortcut will appear in the widget picker. If the shortcut is selected, the Activity here will be
+ * launched, creating a new shortcut for [CreateNoteTaskShortcutActivity], and will finish.
+ *
+ * @see <a
+ * href="https://developer.android.com/develop/ui/views/launch/shortcuts/creating-shortcuts#custom-pinned">Creating
+ * a custom shortcut activity</a>
+ */
+internal class CreateNoteTaskShortcutActivity @Inject constructor() : ComponentActivity() {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ val intent =
+ createShortcutIntent(
+ id = SHORTCUT_ID,
+ shortLabel = getString(R.string.note_task_button_label),
+ intent = LaunchNoteTaskActivity.newIntent(context = this),
+ iconResource = R.drawable.ic_note_task_button,
+ )
+ setResult(Activity.RESULT_OK, intent)
+
+ finish()
+ }
+
+ private fun createShortcutIntent(
+ id: String,
+ shortLabel: String,
+ intent: Intent,
+ @DrawableRes iconResource: Int,
+ ): Intent {
+ val shortcutInfo =
+ ShortcutInfoCompat.Builder(this, id)
+ .setIntent(intent)
+ .setShortLabel(shortLabel)
+ .setLongLived(true)
+ .setIcon(IconCompat.createWithResource(this, iconResource))
+ .build()
+
+ return ShortcutManagerCompat.createShortcutResultIntent(
+ this,
+ shortcutInfo,
+ )
+ }
+
+ private companion object {
+ private const val SHORTCUT_ID = "note-task-shortcut-id"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt
new file mode 100644
index 000000000000..47fe67638cd0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.notetask.shortcut
+
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import com.android.systemui.notetask.NoteTaskController
+import com.android.systemui.notetask.NoteTaskIntentResolver
+import javax.inject.Inject
+
+/** Activity responsible for launching the note experience, and finish. */
+internal class LaunchNoteTaskActivity
+@Inject
+constructor(
+ private val noteTaskController: NoteTaskController,
+) : ComponentActivity() {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ noteTaskController.showNoteTask(isInMultiWindowMode)
+
+ finish()
+ }
+
+ companion object {
+
+ /** Creates a new [Intent] set to start [LaunchNoteTaskActivity]. */
+ fun newIntent(context: Context): Intent {
+ return Intent(context, LaunchNoteTaskActivity::class.java).apply {
+ // Intent's action must be set in shortcuts, or an exception will be thrown.
+ // TODO(b/254606432): Use Intent.ACTION_NOTES instead.
+ action = NoteTaskIntentResolver.NOTES_ACTION
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
index bb2b4419a80a..930de1302c58 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
@@ -18,6 +18,9 @@ package com.android.systemui.qs
import android.app.IActivityManager
import android.app.IForegroundServiceObserver
+import android.app.job.IUserVisibleJobObserver
+import android.app.job.JobScheduler
+import android.app.job.UserVisibleJobSummary
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
@@ -47,6 +50,7 @@ import androidx.recyclerview.widget.RecyclerView
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.TASK_MANAGER_ENABLED
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.TASK_MANAGER_SHOW_FOOTER_DOT
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.TASK_MANAGER_SHOW_USER_VISIBLE_JOBS
import com.android.internal.jank.InteractionJankMonitor
import com.android.systemui.Dumpable
import com.android.systemui.R
@@ -92,6 +96,8 @@ interface FgsManagerController {
*/
val showFooterDot: StateFlow<Boolean>
+ val includesUserVisibleJobs: Boolean
+
/**
* Initialize this controller. This should be called once, before this controller is used for
* the first time.
@@ -116,10 +122,6 @@ interface FgsManagerController {
/** Remove a [OnDialogDismissedListener]. */
fun removeOnDialogDismissedListener(listener: OnDialogDismissedListener)
- /** Whether we should update the footer visibility. */
- // TODO(b/242040009): Remove this.
- fun shouldUpdateFooterVisibility(): Boolean
-
@VisibleForTesting
fun visibleButtonsCount(): Int
@@ -141,19 +143,21 @@ class FgsManagerControllerImpl @Inject constructor(
@Background private val backgroundExecutor: Executor,
private val systemClock: SystemClock,
private val activityManager: IActivityManager,
+ private val jobScheduler: JobScheduler,
private val packageManager: PackageManager,
private val userTracker: UserTracker,
private val deviceConfigProxy: DeviceConfigProxy,
private val dialogLaunchAnimator: DialogLaunchAnimator,
private val broadcastDispatcher: BroadcastDispatcher,
private val dumpManager: DumpManager
-) : IForegroundServiceObserver.Stub(), Dumpable, FgsManagerController {
+) : Dumpable, FgsManagerController {
companion object {
private const val INTERACTION_JANK_TAG = "active_background_apps"
private const val DEFAULT_TASK_MANAGER_ENABLED = true
private const val DEFAULT_TASK_MANAGER_SHOW_FOOTER_DOT = false
private const val DEFAULT_TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS = true
+ private const val DEFAULT_TASK_MANAGER_SHOW_USER_VISIBLE_JOBS = false
}
override var newChangesSinceDialogWasDismissed = false
@@ -167,6 +171,11 @@ class FgsManagerControllerImpl @Inject constructor(
private var showStopBtnForUserAllowlistedApps = false
+ private var showUserVisibleJobs = DEFAULT_TASK_MANAGER_SHOW_USER_VISIBLE_JOBS
+
+ override val includesUserVisibleJobs: Boolean
+ get() = showUserVisibleJobs
+
override val numRunningPackages: Int
get() {
synchronized(lock) {
@@ -186,7 +195,7 @@ class FgsManagerControllerImpl @Inject constructor(
private var currentProfileIds = mutableSetOf<Int>()
@GuardedBy("lock")
- private val runningServiceTokens = mutableMapOf<UserPackage, StartTimeAndTokens>()
+ private val runningTaskIdentifiers = mutableMapOf<UserPackage, StartTimeAndIdentifiers>()
@GuardedBy("lock")
private var dialog: SystemUIDialog? = null
@@ -210,13 +219,29 @@ class FgsManagerControllerImpl @Inject constructor(
}
}
+ private val foregroundServiceObserver = ForegroundServiceObserver()
+
+ private val userVisibleJobObserver = UserVisibleJobObserver()
+
override fun init() {
synchronized(lock) {
if (initialized) {
return
}
+
+ showUserVisibleJobs = deviceConfigProxy.getBoolean(
+ NAMESPACE_SYSTEMUI,
+ TASK_MANAGER_SHOW_USER_VISIBLE_JOBS, DEFAULT_TASK_MANAGER_SHOW_USER_VISIBLE_JOBS)
+
try {
- activityManager.registerForegroundServiceObserver(this)
+ activityManager.registerForegroundServiceObserver(foregroundServiceObserver)
+ // Clumping FGS and user-visible jobs here and showing a single entry and button
+ // for them is the easiest way to get user-visible jobs showing in Task Manager.
+ // Ideally, we would have dedicated UI in task manager for the user-visible jobs.
+ // TODO(255768978): distinguish jobs from FGS and give users more control
+ if (showUserVisibleJobs) {
+ jobScheduler.registerUserVisibleJobObserver(userVisibleJobObserver)
+ }
} catch (e: RemoteException) {
e.rethrowFromSystemServer()
}
@@ -235,6 +260,12 @@ class FgsManagerControllerImpl @Inject constructor(
showStopBtnForUserAllowlistedApps = it.getBoolean(
TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS,
showStopBtnForUserAllowlistedApps)
+ var wasShowingUserVisibleJobs = showUserVisibleJobs
+ showUserVisibleJobs = it.getBoolean(
+ TASK_MANAGER_SHOW_USER_VISIBLE_JOBS, showUserVisibleJobs)
+ if (showUserVisibleJobs != wasShowingUserVisibleJobs) {
+ onShowUserVisibleJobsFlagChanged()
+ }
}
_isAvailable.value = deviceConfigProxy.getBoolean(
@@ -269,32 +300,6 @@ class FgsManagerControllerImpl @Inject constructor(
}
}
- override fun onForegroundStateChanged(
- token: IBinder,
- packageName: String,
- userId: Int,
- isForeground: Boolean
- ) {
- synchronized(lock) {
- val userPackageKey = UserPackage(userId, packageName)
- if (isForeground) {
- runningServiceTokens.getOrPut(userPackageKey) { StartTimeAndTokens(systemClock) }
- .addToken(token)
- } else {
- if (runningServiceTokens[userPackageKey]?.also {
- it.removeToken(token)
- }?.isEmpty() == true
- ) {
- runningServiceTokens.remove(userPackageKey)
- }
- }
-
- updateNumberOfVisibleRunningPackagesLocked()
-
- updateAppItemsLocked()
- }
- }
-
@GuardedBy("lock")
private val onNumberOfPackagesChangedListeners =
mutableSetOf<FgsManagerController.OnNumberOfPackagesChangedListener>()
@@ -336,7 +341,7 @@ class FgsManagerControllerImpl @Inject constructor(
}
private fun getNumVisiblePackagesLocked(): Int {
- return runningServiceTokens.keys.count {
+ return runningTaskIdentifiers.keys.count {
it.uiControl != UIControl.HIDE_ENTRY && currentProfileIds.contains(it.userId)
}
}
@@ -361,18 +366,16 @@ class FgsManagerControllerImpl @Inject constructor(
}
private fun getNumVisibleButtonsLocked(): Int {
- return runningServiceTokens.keys.count {
+ return runningTaskIdentifiers.keys.count {
it.uiControl != UIControl.HIDE_BUTTON && currentProfileIds.contains(it.userId)
}
}
- override fun shouldUpdateFooterVisibility() = dialog == null
-
override fun showDialog(expandable: Expandable?) {
synchronized(lock) {
if (dialog == null) {
- runningServiceTokens.keys.forEach {
+ runningTaskIdentifiers.keys.forEach {
it.updateUiControl()
}
@@ -434,17 +437,17 @@ class FgsManagerControllerImpl @Inject constructor(
return
}
- val addedPackages = runningServiceTokens.keys.filter {
+ val addedPackages = runningTaskIdentifiers.keys.filter {
currentProfileIds.contains(it.userId) &&
it.uiControl != UIControl.HIDE_ENTRY && runningApps[it]?.stopped != true
}
- val removedPackages = runningApps.keys.filter { !runningServiceTokens.containsKey(it) }
+ val removedPackages = runningApps.keys.filter { !runningTaskIdentifiers.containsKey(it) }
addedPackages.forEach {
val ai = packageManager.getApplicationInfoAsUser(it.packageName, 0, it.userId)
runningApps[it] = RunningApp(
it.userId, it.packageName,
- runningServiceTokens[it]!!.startTime, it.uiControl,
+ runningTaskIdentifiers[it]!!.startTime, it.uiControl,
packageManager.getApplicationLabel(ai),
packageManager.getUserBadgedIcon(
packageManager.getApplicationIcon(ai), UserHandle.of(it.userId)
@@ -471,7 +474,41 @@ class FgsManagerControllerImpl @Inject constructor(
private fun stopPackage(userId: Int, packageName: String, timeStarted: Long) {
logEvent(stopped = true, packageName, userId, timeStarted)
- activityManager.stopAppForUser(packageName, userId)
+ val userPackageKey = UserPackage(userId, packageName)
+ if (showUserVisibleJobs &&
+ runningTaskIdentifiers[userPackageKey]?.hasRunningJobs() == true) {
+ // TODO(255768978): allow fine-grained job control
+ jobScheduler.stopUserVisibleJobsForUser(packageName, userId)
+ }
+ if (runningTaskIdentifiers[userPackageKey]?.hasFgs() == true) {
+ activityManager.stopAppForUser(packageName, userId)
+ }
+ }
+
+ private fun onShowUserVisibleJobsFlagChanged() {
+ if (showUserVisibleJobs) {
+ jobScheduler.registerUserVisibleJobObserver(userVisibleJobObserver)
+ } else {
+ jobScheduler.unregisterUserVisibleJobObserver(userVisibleJobObserver)
+
+ synchronized(lock) {
+ for ((userPackage, startTimeAndIdentifiers) in runningTaskIdentifiers) {
+ if (startTimeAndIdentifiers.hasFgs()) {
+ // The app still has FGS running, so all we need to do is remove
+ // the job summaries
+ startTimeAndIdentifiers.clearJobSummaries()
+ } else {
+ // The app only has user-visible jobs running, so remove it from
+ // the map altogether
+ runningTaskIdentifiers.remove(userPackage)
+ }
+ }
+
+ updateNumberOfVisibleRunningPackagesLocked()
+
+ updateAppItemsLocked()
+ }
+ }
}
private fun logEvent(stopped: Boolean, packageName: String, userId: Int, timeStarted: Long) {
@@ -564,6 +601,62 @@ class FgsManagerControllerImpl @Inject constructor(
}
}
+ private inner class ForegroundServiceObserver : IForegroundServiceObserver.Stub() {
+ override fun onForegroundStateChanged(
+ token: IBinder,
+ packageName: String,
+ userId: Int,
+ isForeground: Boolean
+ ) {
+ synchronized(lock) {
+ val userPackageKey = UserPackage(userId, packageName)
+ if (isForeground) {
+ runningTaskIdentifiers
+ .getOrPut(userPackageKey) { StartTimeAndIdentifiers(systemClock) }
+ .addFgsToken(token)
+ } else {
+ if (runningTaskIdentifiers[userPackageKey]?.also {
+ it.removeFgsToken(token)
+ }?.isEmpty() == true
+ ) {
+ runningTaskIdentifiers.remove(userPackageKey)
+ }
+ }
+
+ updateNumberOfVisibleRunningPackagesLocked()
+
+ updateAppItemsLocked()
+ }
+ }
+ }
+
+ private inner class UserVisibleJobObserver : IUserVisibleJobObserver.Stub() {
+ override fun onUserVisibleJobStateChanged(
+ summary: UserVisibleJobSummary,
+ isRunning: Boolean
+ ) {
+ synchronized(lock) {
+ val userPackageKey = UserPackage(summary.sourceUserId, summary.sourcePackageName)
+ if (isRunning) {
+ runningTaskIdentifiers
+ .getOrPut(userPackageKey) { StartTimeAndIdentifiers(systemClock) }
+ .addJobSummary(summary)
+ } else {
+ if (runningTaskIdentifiers[userPackageKey]?.also {
+ it.removeJobSummary(summary)
+ }?.isEmpty() == true
+ ) {
+ runningTaskIdentifiers.remove(userPackageKey)
+ }
+ }
+
+ updateNumberOfVisibleRunningPackagesLocked()
+
+ updateAppItemsLocked()
+ }
+ }
+ }
+
private inner class UserPackage(
val userId: Int,
val packageName: String
@@ -630,37 +723,64 @@ class FgsManagerControllerImpl @Inject constructor(
}
}
- private data class StartTimeAndTokens(
+ private data class StartTimeAndIdentifiers(
val systemClock: SystemClock
) {
val startTime = systemClock.elapsedRealtime()
- val tokens = mutableSetOf<IBinder>()
+ val fgsTokens = mutableSetOf<IBinder>()
+ val jobSummaries = mutableSetOf<UserVisibleJobSummary>()
- fun addToken(token: IBinder) {
- tokens.add(token)
+ fun addJobSummary(summary: UserVisibleJobSummary) {
+ jobSummaries.add(summary)
}
- fun removeToken(token: IBinder) {
- tokens.remove(token)
+ fun clearJobSummaries() {
+ jobSummaries.clear()
+ }
+
+ fun removeJobSummary(summary: UserVisibleJobSummary) {
+ jobSummaries.remove(summary)
+ }
+
+ fun addFgsToken(token: IBinder) {
+ fgsTokens.add(token)
+ }
+
+ fun removeFgsToken(token: IBinder) {
+ fgsTokens.remove(token)
+ }
+
+ fun hasFgs(): Boolean {
+ return !fgsTokens.isEmpty()
+ }
+
+ fun hasRunningJobs(): Boolean {
+ return !jobSummaries.isEmpty()
}
fun isEmpty(): Boolean {
- return tokens.isEmpty()
+ return fgsTokens.isEmpty() && jobSummaries.isEmpty()
}
fun dump(pw: PrintWriter) {
- pw.println("StartTimeAndTokens: [")
+ pw.println("StartTimeAndIdentifiers: [")
pw.indentIfPossible {
pw.println(
"startTime=$startTime (time running =" +
" ${systemClock.elapsedRealtime() - startTime}ms)"
)
- pw.println("tokens: [")
+ pw.println("fgs tokens: [")
pw.indentIfPossible {
- for (token in tokens) {
+ for (token in fgsTokens) {
pw.println("$token")
}
}
+ pw.println("job summaries: [")
+ pw.indentIfPossible {
+ for (summary in jobSummaries) {
+ pw.println("$summary")
+ }
+ }
pw.println("]")
}
pw.println("]")
@@ -724,13 +844,13 @@ class FgsManagerControllerImpl @Inject constructor(
synchronized(lock) {
pw.println("current user profiles = $currentProfileIds")
pw.println("newChangesSinceDialogWasShown=$newChangesSinceDialogWasDismissed")
- pw.println("Running service tokens: [")
+ pw.println("Running task identifiers: [")
pw.indentIfPossible {
- runningServiceTokens.forEach { (userPackage, startTimeAndTokens) ->
+ runningTaskIdentifiers.forEach { (userPackage, startTimeAndIdentifiers) ->
pw.println("{")
pw.indentIfPossible {
userPackage.dump(pw)
- startTimeAndTokens.dump(pw)
+ startTimeAndIdentifiers.dump(pw)
}
pw.println("}")
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
index a9943e886339..b52233fc748b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,261 +16,17 @@
package com.android.systemui.qs
-import android.content.Intent
-import android.content.res.Configuration
-import android.os.Handler
-import android.os.UserManager
-import android.provider.Settings
-import android.provider.Settings.Global.USER_SWITCHER_ENABLED
-import android.view.View
-import android.view.ViewGroup
-import androidx.annotation.VisibleForTesting
-import com.android.internal.jank.InteractionJankMonitor
-import com.android.internal.logging.MetricsLogger
-import com.android.internal.logging.UiEventLogger
-import com.android.internal.logging.nano.MetricsProto
-import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.systemui.R
-import com.android.systemui.animation.ActivityLaunchAnimator
-import com.android.systemui.animation.Expandable
-import com.android.systemui.globalactions.GlobalActionsDialogLite
-import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.plugins.FalsingManager
-import com.android.systemui.qs.dagger.QSFlagsModule.PM_LITE_ENABLED
-import com.android.systemui.qs.dagger.QSScope
-import com.android.systemui.settings.UserTracker
-import com.android.systemui.statusbar.phone.MultiUserSwitchController
-import com.android.systemui.statusbar.policy.ConfigurationController
-import com.android.systemui.statusbar.policy.DeviceProvisionedController
-import com.android.systemui.statusbar.policy.UserInfoController
-import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener
-import com.android.systemui.util.LargeScreenUtils
-import com.android.systemui.util.ViewController
-import com.android.systemui.util.settings.GlobalSettings
+import com.android.systemui.dagger.SysUISingleton
import javax.inject.Inject
-import javax.inject.Named
-import javax.inject.Provider
-/**
- * Manages [FooterActionsView] behaviour, both when it's placed in QS or QQS (split shade).
- * Main difference between QS and QQS behaviour is condition when buttons should be visible,
- * determined by [buttonsVisibleState]
- */
-@QSScope
-// TODO(b/242040009): Remove this file.
-internal class FooterActionsController @Inject constructor(
- view: FooterActionsView,
- multiUserSwitchControllerFactory: MultiUserSwitchController.Factory,
- private val activityStarter: ActivityStarter,
- private val userManager: UserManager,
- private val userTracker: UserTracker,
- private val userInfoController: UserInfoController,
- private val deviceProvisionedController: DeviceProvisionedController,
- private val securityFooterController: QSSecurityFooter,
- private val fgsManagerFooterController: QSFgsManagerFooter,
- private val falsingManager: FalsingManager,
- private val metricsLogger: MetricsLogger,
- private val globalActionsDialogProvider: Provider<GlobalActionsDialogLite>,
- private val uiEventLogger: UiEventLogger,
- @Named(PM_LITE_ENABLED) private val showPMLiteButton: Boolean,
- private val globalSetting: GlobalSettings,
- private val handler: Handler,
- private val configurationController: ConfigurationController,
-) : ViewController<FooterActionsView>(view) {
-
- private var globalActionsDialog: GlobalActionsDialogLite? = null
-
- private var lastExpansion = -1f
- private var listening: Boolean = false
- private var inSplitShade = false
-
- private val singleShadeAnimator by lazy {
- // In single shade, the actions footer should only appear at the end of the expansion,
- // so that it doesn't overlap with the notifications panel.
- TouchAnimator.Builder().addFloat(mView, "alpha", 0f, 1f).setStartDelay(0.9f).build()
- }
-
- private val splitShadeAnimator by lazy {
- // The Actions footer view has its own background which is the same color as the qs panel's
- // background.
- // We don't want it to fade in at the same time as the rest of the panel, otherwise it is
- // more opaque than the rest of the panel's background. Only applies to split shade.
- val alphaAnimator = TouchAnimator.Builder().addFloat(mView, "alpha", 0f, 1f).build()
- val bgAlphaAnimator =
- TouchAnimator.Builder()
- .addFloat(mView, "backgroundAlpha", 0f, 1f)
- .setStartDelay(0.9f)
- .build()
- // In split shade, we want the actions footer to fade in exactly at the same time as the
- // rest of the shade, as there is no overlap.
- TouchAnimator.Builder()
- .addFloat(alphaAnimator, "position", 0f, 1f)
- .addFloat(bgAlphaAnimator, "position", 0f, 1f)
- .build()
- }
-
- private val animators: TouchAnimator
- get() = if (inSplitShade) splitShadeAnimator else singleShadeAnimator
-
- var visible = true
- set(value) {
- field = value
- updateVisibility()
- }
-
- private val settingsButtonContainer: View = view.findViewById(R.id.settings_button_container)
- private val securityFootersContainer: ViewGroup? =
- view.findViewById(R.id.security_footers_container)
- private val powerMenuLite: View = view.findViewById(R.id.pm_lite)
- private val multiUserSwitchController = multiUserSwitchControllerFactory.create(view)
-
- @VisibleForTesting
- internal val securityFootersSeparator = View(context).apply { visibility = View.GONE }
-
- private val onUserInfoChangedListener = OnUserInfoChangedListener { _, picture, _ ->
- val isGuestUser: Boolean = userManager.isGuestUser(KeyguardUpdateMonitor.getCurrentUser())
- mView.onUserInfoChanged(picture, isGuestUser)
- }
-
- private val multiUserSetting =
- object : SettingObserver(
- globalSetting, handler, USER_SWITCHER_ENABLED, userTracker.userId) {
- override fun handleValueChanged(value: Int, observedChange: Boolean) {
- if (observedChange) {
- updateView()
- }
- }
- }
-
- private val onClickListener = View.OnClickListener { v ->
- // Don't do anything if the tap looks suspicious.
- if (!visible || falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
- return@OnClickListener
- }
- if (v === settingsButtonContainer) {
- if (!deviceProvisionedController.isCurrentUserSetup) {
- // If user isn't setup just unlock the device and dump them back at SUW.
- activityStarter.postQSRunnableDismissingKeyguard {}
- return@OnClickListener
- }
- metricsLogger.action(MetricsProto.MetricsEvent.ACTION_QS_EXPANDED_SETTINGS_LAUNCH)
- startSettingsActivity()
- } else if (v === powerMenuLite) {
- uiEventLogger.log(GlobalActionsDialogLite.GlobalActionsEvent.GA_OPEN_QS)
- globalActionsDialog?.showOrHideDialog(false, true, Expandable.fromView(powerMenuLite))
- }
- }
-
- private val configurationListener =
- object : ConfigurationController.ConfigurationListener {
- override fun onConfigChanged(newConfig: Configuration?) {
- updateResources()
- }
- }
-
- private fun updateResources() {
- inSplitShade = LargeScreenUtils.shouldUseSplitNotificationShade(resources)
- }
-
- override fun onInit() {
- multiUserSwitchController.init()
- securityFooterController.init()
- fgsManagerFooterController.init()
- }
-
- private fun updateVisibility() {
- val previousVisibility = mView.visibility
- mView.visibility = if (visible) View.VISIBLE else View.INVISIBLE
- if (previousVisibility != mView.visibility) updateView()
- }
-
- private fun startSettingsActivity() {
- val animationController = settingsButtonContainer?.let {
- ActivityLaunchAnimator.Controller.fromView(
- it,
- InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON)
- }
- activityStarter.startActivity(Intent(Settings.ACTION_SETTINGS),
- true /* dismissShade */, animationController)
- }
-
- @VisibleForTesting
- public override fun onViewAttached() {
- globalActionsDialog = globalActionsDialogProvider.get()
- if (showPMLiteButton) {
- powerMenuLite.visibility = View.VISIBLE
- powerMenuLite.setOnClickListener(onClickListener)
- } else {
- powerMenuLite.visibility = View.GONE
- }
- settingsButtonContainer.setOnClickListener(onClickListener)
- multiUserSetting.isListening = true
-
- val securityFooter = securityFooterController.view
- securityFootersContainer?.addView(securityFooter)
- val separatorWidth = resources.getDimensionPixelSize(R.dimen.qs_footer_action_inset)
- securityFootersContainer?.addView(securityFootersSeparator, separatorWidth, 1)
-
- val fgsFooter = fgsManagerFooterController.view
- securityFootersContainer?.addView(fgsFooter)
-
- val visibilityListener =
- VisibilityChangedDispatcher.OnVisibilityChangedListener { visibility ->
- if (securityFooter.visibility == View.VISIBLE &&
- fgsFooter.visibility == View.VISIBLE) {
- securityFootersSeparator.visibility = View.VISIBLE
- } else {
- securityFootersSeparator.visibility = View.GONE
- }
- fgsManagerFooterController
- .setCollapsed(securityFooter.visibility == View.VISIBLE)
- }
- securityFooterController.setOnVisibilityChangedListener(visibilityListener)
- fgsManagerFooterController.setOnVisibilityChangedListener(visibilityListener)
-
- configurationController.addCallback(configurationListener)
-
- updateResources()
- updateView()
- }
-
- private fun updateView() {
- mView.updateEverything(multiUserSwitchController.isMultiUserEnabled)
- }
-
- override fun onViewDetached() {
- globalActionsDialog?.destroy()
- globalActionsDialog = null
- setListening(false)
- multiUserSetting.isListening = false
- configurationController.removeCallback(configurationListener)
- }
-
- fun setListening(listening: Boolean) {
- if (this.listening == listening) {
- return
- }
- this.listening = listening
- if (this.listening) {
- userInfoController.addCallback(onUserInfoChangedListener)
- updateView()
- } else {
- userInfoController.removeCallback(onUserInfoChangedListener)
- }
-
- fgsManagerFooterController.setListening(listening)
- securityFooterController.setListening(listening)
- }
-
- fun disable(state2: Int) {
- mView.disable(state2, multiUserSwitchController.isMultiUserEnabled)
- }
-
- fun setExpansion(headerExpansionFraction: Float) {
- animators.setPosition(headerExpansionFraction)
- }
-
- fun setKeyguardShowing(showing: Boolean) {
- setExpansion(lastExpansion)
+/** Controller for the footer actions. This manages the initialization of its dependencies. */
+@SysUISingleton
+class FooterActionsController
+@Inject
+constructor(
+ private val fgsManagerController: FgsManagerController,
+) {
+ fun init() {
+ fgsManagerController.init()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsView.kt b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsView.kt
deleted file mode 100644
index d602b0b27977..000000000000
--- a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsView.kt
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.qs
-
-import android.app.StatusBarManager
-import android.content.Context
-import android.graphics.PorterDuff
-import android.graphics.drawable.Drawable
-import android.graphics.drawable.RippleDrawable
-import android.os.UserManager
-import android.util.AttributeSet
-import android.util.Log
-import android.view.MotionEvent
-import android.view.View
-import android.widget.ImageView
-import android.widget.LinearLayout
-import androidx.annotation.Keep
-import com.android.settingslib.Utils
-import com.android.settingslib.drawable.UserIconDrawable
-import com.android.systemui.R
-import com.android.systemui.statusbar.phone.MultiUserSwitch
-
-/**
- * Quick Settings bottom buttons placed in footer (aka utility bar) - always visible in expanded QS,
- * in split shade mode visible also in collapsed state. May contain up to 5 buttons: settings,
- * edit tiles, power off and conditionally: user switch and tuner
- */
-// TODO(b/242040009): Remove this file.
-class FooterActionsView(context: Context?, attrs: AttributeSet?) : LinearLayout(context, attrs) {
- private lateinit var settingsContainer: View
- private lateinit var multiUserSwitch: MultiUserSwitch
- private lateinit var multiUserAvatar: ImageView
-
- private var qsDisabled = false
- private var expansionAmount = 0f
-
- /**
- * Sets the alpha of the background of this view.
- *
- * Used from a [TouchAnimator] in the controller.
- */
- var backgroundAlpha: Float = 1f
- @Keep
- set(value) {
- field = value
- background?.alpha = (value * 255).toInt()
- }
- @Keep get
-
- override fun onFinishInflate() {
- super.onFinishInflate()
- settingsContainer = findViewById(R.id.settings_button_container)
- multiUserSwitch = findViewById(R.id.multi_user_switch)
- multiUserAvatar = multiUserSwitch.findViewById(R.id.multi_user_avatar)
-
- // RenderThread is doing more harm than good when touching the header (to expand quick
- // settings), so disable it for this view
- if (settingsContainer.background is RippleDrawable) {
- (settingsContainer.background as RippleDrawable).setForceSoftware(true)
- }
- importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_YES
- }
-
- fun disable(
- state2: Int,
- multiUserEnabled: Boolean
- ) {
- val disabled = state2 and StatusBarManager.DISABLE2_QUICK_SETTINGS != 0
- if (disabled == qsDisabled) return
- qsDisabled = disabled
- updateEverything(multiUserEnabled)
- }
-
- fun updateEverything(
- multiUserEnabled: Boolean
- ) {
- post {
- updateVisibilities(multiUserEnabled)
- updateClickabilities()
- isClickable = false
- }
- }
-
- private fun updateClickabilities() {
- multiUserSwitch.isClickable = multiUserSwitch.visibility == VISIBLE
- settingsContainer.isClickable = settingsContainer.visibility == VISIBLE
- }
-
- private fun updateVisibilities(
- multiUserEnabled: Boolean
- ) {
- settingsContainer.visibility = if (qsDisabled) GONE else VISIBLE
- multiUserSwitch.visibility = if (multiUserEnabled) VISIBLE else GONE
- val isDemo = UserManager.isDeviceInDemoMode(context)
- settingsContainer.visibility = if (isDemo) INVISIBLE else VISIBLE
- }
-
- fun onUserInfoChanged(picture: Drawable?, isGuestUser: Boolean) {
- var pictureToSet = picture
- if (picture != null && isGuestUser && picture !is UserIconDrawable) {
- pictureToSet = picture.constantState.newDrawable(resources).mutate()
- pictureToSet.setColorFilter(
- Utils.getColorAttrDefaultColor(mContext, android.R.attr.colorForeground),
- PorterDuff.Mode.SRC_IN)
- }
- multiUserAvatar.setImageDrawable(pictureToSet)
- }
-
- override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
- if (VERBOSE) Log.d(TAG, "FooterActionsView onInterceptTouchEvent ${ev?.string}")
- return super.onInterceptTouchEvent(ev)
- }
-
- override fun onTouchEvent(event: MotionEvent?): Boolean {
- if (VERBOSE) Log.d(TAG, "FooterActionsView onTouchEvent ${event?.string}")
- return super.onTouchEvent(event)
- }
-}
-private const val TAG = "FooterActionsView"
-private val VERBOSE = Log.isLoggable(TAG, Log.VERBOSE)
-private val MotionEvent.string
- get() = "($id): ($x,$y)"
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index dc9dcc295e6e..0c242d9da25f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -215,7 +215,7 @@ public class QSContainerImpl extends FrameLayout implements Dumpable {
// Some views are always full width or have dependent padding
continue;
}
- if (!(view instanceof FooterActionsView)) {
+ if (view.getId() != R.id.qs_footer_actions) {
// Only padding for FooterActionsView, no margin. That way, the background goes
// all the way to the edge.
LayoutParams lp = (LayoutParams) view.getLayoutParams();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFgsManagerFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSFgsManagerFooter.java
deleted file mode 100644
index b1b9dd721eaf..000000000000
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFgsManagerFooter.java
+++ /dev/null
@@ -1,178 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.qs;
-
-import static com.android.systemui.qs.dagger.QSFragmentModule.QS_FGS_MANAGER_FOOTER_VIEW;
-import static com.android.systemui.util.PluralMessageFormaterKt.icuMessageFormat;
-
-import android.content.Context;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import androidx.annotation.Nullable;
-
-import com.android.systemui.R;
-import com.android.systemui.animation.Expandable;
-import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.qs.dagger.QSScope;
-
-import java.util.concurrent.Executor;
-
-import javax.inject.Inject;
-import javax.inject.Named;
-
-/**
- * Footer entry point for the foreground service manager
- */
-// TODO(b/242040009): Remove this file.
-@QSScope
-public class QSFgsManagerFooter implements View.OnClickListener,
- FgsManagerController.OnDialogDismissedListener,
- FgsManagerController.OnNumberOfPackagesChangedListener,
- VisibilityChangedDispatcher {
-
- private final View mRootView;
- private final TextView mFooterText;
- private final Context mContext;
- private final Executor mMainExecutor;
- private final Executor mExecutor;
-
- private final FgsManagerController mFgsManagerController;
-
- private boolean mIsInitialized = false;
- private int mNumPackages;
-
- private final View mTextContainer;
- private final View mNumberContainer;
- private final TextView mNumberView;
- private final ImageView mDotView;
- private final ImageView mCollapsedDotView;
-
- @Nullable
- private VisibilityChangedDispatcher.OnVisibilityChangedListener mVisibilityChangedListener;
-
- @Inject
- QSFgsManagerFooter(@Named(QS_FGS_MANAGER_FOOTER_VIEW) View rootView,
- @Main Executor mainExecutor, @Background Executor executor,
- FgsManagerController fgsManagerController) {
- mRootView = rootView;
- mFooterText = mRootView.findViewById(R.id.footer_text);
- mTextContainer = mRootView.findViewById(R.id.fgs_text_container);
- mNumberContainer = mRootView.findViewById(R.id.fgs_number_container);
- mNumberView = mRootView.findViewById(R.id.fgs_number);
- mDotView = mRootView.findViewById(R.id.fgs_new);
- mCollapsedDotView = mRootView.findViewById(R.id.fgs_collapsed_new);
- mContext = rootView.getContext();
- mMainExecutor = mainExecutor;
- mExecutor = executor;
- mFgsManagerController = fgsManagerController;
- }
-
- /**
- * Whether to show the footer in collapsed mode (just a number) or not (text).
- * @param collapsed
- */
- public void setCollapsed(boolean collapsed) {
- mTextContainer.setVisibility(collapsed ? View.GONE : View.VISIBLE);
- mNumberContainer.setVisibility(collapsed ? View.VISIBLE : View.GONE);
- LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mRootView.getLayoutParams();
- lp.width = collapsed ? ViewGroup.LayoutParams.WRAP_CONTENT : 0;
- lp.weight = collapsed ? 0f : 1f;
- mRootView.setLayoutParams(lp);
- }
-
- public void init() {
- if (mIsInitialized) {
- return;
- }
-
- mFgsManagerController.init();
-
- mRootView.setOnClickListener(this);
-
- mIsInitialized = true;
- }
-
- public void setListening(boolean listening) {
- if (listening) {
- mFgsManagerController.addOnDialogDismissedListener(this);
- mFgsManagerController.addOnNumberOfPackagesChangedListener(this);
- mNumPackages = mFgsManagerController.getNumRunningPackages();
- refreshState();
- } else {
- mFgsManagerController.removeOnDialogDismissedListener(this);
- mFgsManagerController.removeOnNumberOfPackagesChangedListener(this);
- }
- }
-
- @Override
- public void setOnVisibilityChangedListener(
- @Nullable OnVisibilityChangedListener onVisibilityChangedListener) {
- mVisibilityChangedListener = onVisibilityChangedListener;
- }
-
- @Override
- public void onClick(View view) {
- mFgsManagerController.showDialog(Expandable.fromView(view));
- }
-
- public void refreshState() {
- mExecutor.execute(this::handleRefreshState);
- }
-
- public View getView() {
- return mRootView;
- }
-
- public void handleRefreshState() {
- mMainExecutor.execute(() -> {
- CharSequence text = icuMessageFormat(mContext.getResources(),
- R.string.fgs_manager_footer_label, mNumPackages);
- mFooterText.setText(text);
- mNumberView.setText(Integer.toString(mNumPackages));
- mNumberView.setContentDescription(text);
- if (mFgsManagerController.shouldUpdateFooterVisibility()) {
- mRootView.setVisibility(mNumPackages > 0
- && mFgsManagerController.isAvailable().getValue() ? View.VISIBLE
- : View.GONE);
- int dotVis = mFgsManagerController.getShowFooterDot().getValue()
- && mFgsManagerController.getNewChangesSinceDialogWasDismissed()
- ? View.VISIBLE : View.GONE;
- mDotView.setVisibility(dotVis);
- mCollapsedDotView.setVisibility(dotVis);
- if (mVisibilityChangedListener != null) {
- mVisibilityChangedListener.onVisibilityChanged(mRootView.getVisibility());
- }
- }
- });
- }
-
- @Override
- public void onDialogDismissed() {
- refreshState();
- }
-
- @Override
- public void onNumberOfPackagesChanged(int numPackages) {
- mNumPackages = numPackages;
- refreshState();
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index c0533bae233f..893574a59d94 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -33,6 +33,7 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
+import android.widget.LinearLayout;
import androidx.annotation.FloatRange;
import androidx.annotation.Nullable;
@@ -48,7 +49,6 @@ import com.android.systemui.animation.Interpolators;
import com.android.systemui.animation.ShadeInterpolation;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.media.controls.ui.MediaHost;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.qs.QS;
@@ -114,7 +114,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
private final QSFragmentDisableFlagsLogger mQsFragmentDisableFlagsLogger;
private final QSTileHost mHost;
private final FeatureFlags mFeatureFlags;
- private final NewFooterActionsController mNewFooterActionsController;
+ private final FooterActionsController mFooterActionsController;
private final FooterActionsViewModel.Factory mFooterActionsViewModelFactory;
private final ListeningAndVisibilityLifecycleOwner mListeningAndVisibilityLifecycleOwner;
private boolean mShowCollapsedOnKeyguard;
@@ -132,9 +132,6 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
private QSPanelController mQSPanelController;
private QuickQSPanelController mQuickQSPanelController;
private QSCustomizerController mQSCustomizerController;
- @Nullable
- private FooterActionsController mQSFooterActionController;
- @Nullable
private FooterActionsViewModel mQSFooterActionsViewModel;
@Nullable
private ScrollListener mScrollListener;
@@ -185,7 +182,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
QSFragmentComponent.Factory qsComponentFactory,
QSFragmentDisableFlagsLogger qsFragmentDisableFlagsLogger,
FalsingManager falsingManager, DumpManager dumpManager, FeatureFlags featureFlags,
- NewFooterActionsController newFooterActionsController,
+ FooterActionsController footerActionsController,
FooterActionsViewModel.Factory footerActionsViewModelFactory) {
mRemoteInputQuickSettingsDisabler = remoteInputQsDisabler;
mQsMediaHost = qsMediaHost;
@@ -199,7 +196,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
mStatusBarStateController = statusBarStateController;
mDumpManager = dumpManager;
mFeatureFlags = featureFlags;
- mNewFooterActionsController = newFooterActionsController;
+ mFooterActionsController = footerActionsController;
mFooterActionsViewModelFactory = footerActionsViewModelFactory;
mListeningAndVisibilityLifecycleOwner = new ListeningAndVisibilityLifecycleOwner();
}
@@ -226,18 +223,12 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
mQSPanelController.init();
mQuickQSPanelController.init();
- if (mFeatureFlags.isEnabled(Flags.NEW_FOOTER_ACTIONS)) {
- mQSFooterActionsViewModel = mFooterActionsViewModelFactory.create(/* lifecycleOwner */
- this);
- FooterActionsView footerActionsView = view.findViewById(R.id.qs_footer_actions);
- FooterActionsViewBinder.bind(footerActionsView, mQSFooterActionsViewModel,
- mListeningAndVisibilityLifecycleOwner);
-
- mNewFooterActionsController.init();
- } else {
- mQSFooterActionController = qsFragmentComponent.getQSFooterActionController();
- mQSFooterActionController.init();
- }
+ mQSFooterActionsViewModel = mFooterActionsViewModelFactory.create(/* lifecycleOwner */
+ this);
+ LinearLayout footerActionsView = view.findViewById(R.id.qs_footer_actions);
+ FooterActionsViewBinder.bind(footerActionsView, mQSFooterActionsViewModel,
+ mListeningAndVisibilityLifecycleOwner);
+ mFooterActionsController.init();
mQSPanelScrollView = view.findViewById(R.id.expanded_qs_scroll_view);
mQSPanelScrollView.addOnLayoutChangeListener(
@@ -436,9 +427,6 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
mContainer.disable(state1, state2, animate);
mHeader.disable(state1, state2, animate);
mFooter.disable(state1, state2, animate);
- if (mQSFooterActionController != null) {
- mQSFooterActionController.disable(state2);
- }
updateQsState();
}
@@ -457,11 +445,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
boolean footerVisible = qsPanelVisible && (mQsExpanded || !keyguardShowing
|| mHeaderAnimating || mShowCollapsedOnKeyguard);
mFooter.setVisibility(footerVisible ? View.VISIBLE : View.INVISIBLE);
- if (mQSFooterActionController != null) {
- mQSFooterActionController.setVisible(footerVisible);
- } else {
- mQSFooterActionsViewModel.onVisibilityChangeRequested(footerVisible);
- }
+ mQSFooterActionsViewModel.onVisibilityChangeRequested(footerVisible);
mFooter.setExpanded((keyguardShowing && !mHeaderAnimating && !mShowCollapsedOnKeyguard)
|| (mQsExpanded && !mStackScrollerOverscrolling));
mQSPanelController.setVisibility(qsPanelVisible ? View.VISIBLE : View.INVISIBLE);
@@ -534,9 +518,6 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
}
mFooter.setKeyguardShowing(keyguardShowing);
- if (mQSFooterActionController != null) {
- mQSFooterActionController.setKeyguardShowing(keyguardShowing);
- }
updateQsState();
}
@@ -552,9 +533,6 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
if (DEBUG) Log.d(TAG, "setListening " + listening);
mListening = listening;
mQSContainerImplController.setListening(listening && mQsVisible);
- if (mQSFooterActionController != null) {
- mQSFooterActionController.setListening(listening && mQsVisible);
- }
mListeningAndVisibilityLifecycleOwner.updateState();
updateQsPanelControllerListening();
}
@@ -665,12 +643,8 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
mFooter.setExpansion(onKeyguardAndExpanded ? 1 : expansion);
float footerActionsExpansion =
onKeyguardAndExpanded ? 1 : mInSplitShade ? alphaProgress : expansion;
- if (mQSFooterActionController != null) {
- mQSFooterActionController.setExpansion(footerActionsExpansion);
- } else {
- mQSFooterActionsViewModel.onQuickSettingsExpansionChanged(footerActionsExpansion,
- mInSplitShade);
- }
+ mQSFooterActionsViewModel.onQuickSettingsExpansionChanged(footerActionsExpansion,
+ mInSplitShade);
mQSPanelController.setRevealExpansion(expansion);
mQSPanelController.getTileLayout().setExpansion(expansion, proposedTranslation);
mQuickQSPanelController.getTileLayout().setExpansion(expansion, proposedTranslation);
@@ -835,11 +809,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
boolean customizing = isCustomizing();
mQSPanelScrollView.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE);
mFooter.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE);
- if (mQSFooterActionController != null) {
- mQSFooterActionController.setVisible(!customizing);
- } else {
- mQSFooterActionsViewModel.onVisibilityChangeRequested(!customizing);
- }
+ mQSFooterActionsViewModel.onVisibilityChangeRequested(!customizing);
mHeader.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE);
// Let the panel know the position changed and it needs to update where notifications
// and whatnot are.
@@ -927,6 +897,11 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
updateShowCollapsedOnKeyguard();
}
+ @VisibleForTesting
+ public ListeningAndVisibilityLifecycleOwner getListeningAndVisibilityLifecycleOwner() {
+ return mListeningAndVisibilityLifecycleOwner;
+ }
+
@Override
public void dump(PrintWriter pw, String[] args) {
IndentingPrintWriter indentingPw = new IndentingPrintWriter(pw, /* singleIndent= */ " ");
@@ -994,7 +969,8 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
* - STARTED when mListening == true && mQsVisible == false.
* - RESUMED when mListening == true && mQsVisible == true.
*/
- private class ListeningAndVisibilityLifecycleOwner implements LifecycleOwner {
+ @VisibleForTesting
+ class ListeningAndVisibilityLifecycleOwner implements LifecycleOwner {
private final LifecycleRegistry mLifecycleRegistry = new LifecycleRegistry(this);
private boolean mDestroyed = false;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
deleted file mode 100644
index 6c1e95645550..000000000000
--- a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
+++ /dev/null
@@ -1,262 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.qs;
-
-import static com.android.systemui.qs.dagger.QSFragmentModule.QS_SECURITY_FOOTER_VIEW;
-
-import android.app.admin.DevicePolicyEventLogger;
-import android.app.admin.DevicePolicyManager;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.res.Resources;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.os.UserHandle;
-import android.util.Log;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import androidx.annotation.Nullable;
-
-import com.android.internal.util.FrameworkStatsLog;
-import com.android.systemui.FontSizeUtils;
-import com.android.systemui.R;
-import com.android.systemui.animation.Expandable;
-import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.common.shared.model.Icon;
-import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.qs.dagger.QSScope;
-import com.android.systemui.qs.footer.domain.model.SecurityButtonConfig;
-import com.android.systemui.security.data.model.SecurityModel;
-import com.android.systemui.statusbar.policy.SecurityController;
-import com.android.systemui.util.ViewController;
-
-import javax.inject.Inject;
-import javax.inject.Named;
-
-/** ViewController for the footer actions. */
-// TODO(b/242040009): Remove this class.
-@QSScope
-public class QSSecurityFooter extends ViewController<View>
- implements OnClickListener, VisibilityChangedDispatcher {
- protected static final String TAG = "QSSecurityFooter";
-
- private final TextView mFooterText;
- private final ImageView mPrimaryFooterIcon;
- private Context mContext;
- private final Callback mCallback = new Callback();
- private final SecurityController mSecurityController;
- private final Handler mMainHandler;
- private final BroadcastDispatcher mBroadcastDispatcher;
- private final QSSecurityFooterUtils mQSSecurityFooterUtils;
-
- protected H mHandler;
-
- private boolean mIsVisible;
- private boolean mIsClickable;
- @Nullable
- private CharSequence mFooterTextContent = null;
- private Icon mFooterIcon;
-
- @Nullable
- private VisibilityChangedDispatcher.OnVisibilityChangedListener mVisibilityChangedListener;
-
- private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (intent.getAction().equals(
- DevicePolicyManager.ACTION_SHOW_DEVICE_MONITORING_DIALOG)) {
- showDeviceMonitoringDialog();
- }
- }
- };
-
- @Inject
- QSSecurityFooter(@Named(QS_SECURITY_FOOTER_VIEW) View rootView,
- @Main Handler mainHandler, SecurityController securityController,
- @Background Looper bgLooper, BroadcastDispatcher broadcastDispatcher,
- QSSecurityFooterUtils qSSecurityFooterUtils) {
- super(rootView);
- mFooterText = mView.findViewById(R.id.footer_text);
- mPrimaryFooterIcon = mView.findViewById(R.id.primary_footer_icon);
- mFooterIcon = new Icon.Resource(
- R.drawable.ic_info_outline, /* contentDescription= */ null);
- mContext = rootView.getContext();
- mSecurityController = securityController;
- mMainHandler = mainHandler;
- mHandler = new H(bgLooper);
- mBroadcastDispatcher = broadcastDispatcher;
- mQSSecurityFooterUtils = qSSecurityFooterUtils;
- }
-
- @Override
- protected void onViewAttached() {
- // Use background handler, as it's the same thread that handleClick is called on.
- mBroadcastDispatcher.registerReceiverWithHandler(mReceiver,
- new IntentFilter(DevicePolicyManager.ACTION_SHOW_DEVICE_MONITORING_DIALOG),
- mHandler, UserHandle.ALL);
- mView.setOnClickListener(this);
- }
-
- @Override
- protected void onViewDetached() {
- mBroadcastDispatcher.unregisterReceiver(mReceiver);
- mView.setOnClickListener(null);
- }
-
- public void setListening(boolean listening) {
- if (listening) {
- mSecurityController.addCallback(mCallback);
- refreshState();
- } else {
- mSecurityController.removeCallback(mCallback);
- }
- }
-
- @Override
- public void setOnVisibilityChangedListener(
- @Nullable OnVisibilityChangedListener onVisibilityChangedListener) {
- mVisibilityChangedListener = onVisibilityChangedListener;
- }
-
- public void onConfigurationChanged() {
- FontSizeUtils.updateFontSize(mFooterText, R.dimen.qs_tile_text_size);
- Resources r = mContext.getResources();
-
- int padding = r.getDimensionPixelSize(R.dimen.qs_footer_padding);
- mView.setPaddingRelative(padding, 0, padding, 0);
- mView.setBackground(mContext.getDrawable(R.drawable.qs_security_footer_background));
- }
-
- public View getView() {
- return mView;
- }
-
- public boolean hasFooter() {
- return mView.getVisibility() != View.GONE;
- }
-
- @Override
- public void onClick(View v) {
- if (!hasFooter()) return;
- mHandler.sendEmptyMessage(H.CLICK);
- }
-
- private void handleClick() {
- showDeviceMonitoringDialog();
- DevicePolicyEventLogger
- .createEvent(FrameworkStatsLog.DEVICE_POLICY_EVENT__EVENT_ID__DO_USER_INFO_CLICKED)
- .write();
- }
-
- // TODO(b/242040009): Remove this.
- public void showDeviceMonitoringDialog() {
- mQSSecurityFooterUtils.showDeviceMonitoringDialog(mContext, Expandable.fromView(mView));
- }
-
- public void refreshState() {
- mHandler.sendEmptyMessage(H.REFRESH_STATE);
- }
-
- private void handleRefreshState() {
- SecurityModel securityModel = SecurityModel.create(mSecurityController);
- SecurityButtonConfig buttonConfig = mQSSecurityFooterUtils.getButtonConfig(securityModel);
-
- if (buttonConfig == null) {
- mIsVisible = false;
- } else {
- mIsVisible = true;
- mIsClickable = buttonConfig.isClickable();
- mFooterTextContent = buttonConfig.getText();
- mFooterIcon = buttonConfig.getIcon();
- }
-
- // Update the UI.
- mMainHandler.post(mUpdatePrimaryIcon);
- mMainHandler.post(mUpdateDisplayState);
- }
-
- private final Runnable mUpdatePrimaryIcon = new Runnable() {
- @Override
- public void run() {
- if (mFooterIcon instanceof Icon.Loaded) {
- mPrimaryFooterIcon.setImageDrawable(((Icon.Loaded) mFooterIcon).getDrawable());
- } else if (mFooterIcon instanceof Icon.Resource) {
- mPrimaryFooterIcon.setImageResource(((Icon.Resource) mFooterIcon).getRes());
- }
- }
- };
-
- private final Runnable mUpdateDisplayState = new Runnable() {
- @Override
- public void run() {
- if (mFooterTextContent != null) {
- mFooterText.setText(mFooterTextContent);
- }
- mView.setVisibility(mIsVisible ? View.VISIBLE : View.GONE);
- if (mVisibilityChangedListener != null) {
- mVisibilityChangedListener.onVisibilityChanged(mView.getVisibility());
- }
-
- if (mIsVisible && mIsClickable) {
- mView.setClickable(true);
- mView.findViewById(R.id.footer_icon).setVisibility(View.VISIBLE);
- } else {
- mView.setClickable(false);
- mView.findViewById(R.id.footer_icon).setVisibility(View.GONE);
- }
- }
- };
-
- private class Callback implements SecurityController.SecurityControllerCallback {
- @Override
- public void onStateChanged() {
- refreshState();
- }
- }
-
- private class H extends Handler {
- private static final int CLICK = 0;
- private static final int REFRESH_STATE = 1;
-
- private H(Looper looper) {
- super(looper);
- }
-
- @Override
- public void handleMessage(Message msg) {
- String name = null;
- try {
- if (msg.what == REFRESH_STATE) {
- name = "handleRefreshState";
- handleRefreshState();
- } else if (msg.what == CLICK) {
- name = "handleClick";
- handleClick();
- }
- } catch (Throwable t) {
- final String error = "Error in " + name;
- Log.w(TAG, error, t);
- }
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooterUtils.java b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooterUtils.java
index 67bc76998597..5dbf0f8dcdce 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooterUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooterUtils.java
@@ -247,7 +247,7 @@ public class QSSecurityFooterUtils implements DialogInterface.OnClickListener {
Icon icon;
ContentDescription contentDescription = null;
- if (isParentalControlsEnabled) {
+ if (isParentalControlsEnabled && securityModel.getDeviceAdminIcon() != null) {
icon = new Icon.Loaded(securityModel.getDeviceAdminIcon(), contentDescription);
} else if (vpnName != null || vpnNameWorkProfile != null) {
if (securityModel.isVpnBranded()) {
@@ -476,7 +476,7 @@ public class QSSecurityFooterUtils implements DialogInterface.OnClickListener {
@VisibleForTesting
View createDialogView(Context quickSettingsContext) {
if (mSecurityController.isParentalControlsEnabled()) {
- return createParentalControlsDialogView();
+ return createParentalControlsDialogView(quickSettingsContext);
}
return createOrganizationDialogView(quickSettingsContext);
}
@@ -579,8 +579,8 @@ public class QSSecurityFooterUtils implements DialogInterface.OnClickListener {
return dialogView;
}
- private View createParentalControlsDialogView() {
- View dialogView = LayoutInflater.from(mContext)
+ private View createParentalControlsDialogView(Context quickSettingsContext) {
+ View dialogView = LayoutInflater.from(quickSettingsContext)
.inflate(R.layout.quick_settings_footer_dialog_parental_controls, null, false);
DeviceAdminInfo info = mSecurityController.getDeviceAdminInfo();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index 6240c10a9d5b..cad296b671b3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -608,7 +608,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, P
if (TextUtils.isEmpty(tileList)) {
tileList = res.getString(R.string.quick_settings_tiles);
- if (DEBUG) Log.d(TAG, "Loaded tile specs from config: " + tileList);
+ if (DEBUG) Log.d(TAG, "Loaded tile specs from default config: " + tileList);
} else {
if (DEBUG) Log.d(TAG, "Loaded tile specs from setting: " + tileList);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java
index aa505fb0b6bd..01eb636f75d6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java
@@ -28,7 +28,6 @@ import com.android.systemui.battery.BatteryMeterView;
import com.android.systemui.dagger.qualifiers.RootView;
import com.android.systemui.plugins.qs.QS;
import com.android.systemui.privacy.OngoingPrivacyChip;
-import com.android.systemui.qs.FooterActionsView;
import com.android.systemui.qs.QSContainerImpl;
import com.android.systemui.qs.QSFooter;
import com.android.systemui.qs.QSFooterView;
@@ -51,8 +50,6 @@ import dagger.Provides;
*/
@Module
public interface QSFragmentModule {
- String QS_FGS_MANAGER_FOOTER_VIEW = "qs_fgs_manager_footer";
- String QS_SECURITY_FOOTER_VIEW = "qs_security_footer";
String QS_USING_MEDIA_PLAYER = "qs_using_media_player";
String QS_USING_COLLAPSED_LANDSCAPE_MEDIA = "qs_using_collapsed_landscape_media";
@@ -119,16 +116,6 @@ public interface QSFragmentModule {
return view.findViewById(R.id.qs_footer);
}
- /**
- * Provides a {@link FooterActionsView}.
- *
- * This will replace a ViewStub either in {@link QSFooterView} or in {@link QSContainerImpl}.
- */
- @Provides
- static FooterActionsView providesQSFooterActionsView(@RootView View view) {
- return view.findViewById(R.id.qs_footer_actions);
- }
-
/** */
@Provides
@QSScope
@@ -146,18 +133,6 @@ public interface QSFragmentModule {
/** */
@Provides
- @QSScope
- @Named(QS_SECURITY_FOOTER_VIEW)
- static View providesQSSecurityFooterView(
- @QSThemedContext LayoutInflater layoutInflater,
- FooterActionsView footerActionsView
- ) {
- return layoutInflater.inflate(R.layout.quick_settings_security_footer, footerActionsView,
- false);
- }
-
- /** */
- @Provides
@Named(QS_USING_MEDIA_PLAYER)
static boolean providesQSUsingMediaPlayer(Context context) {
return useQsMediaPlayer(context);
@@ -183,15 +158,4 @@ public interface QSFragmentModule {
static StatusIconContainer providesStatusIconContainer(QuickStatusBarHeader qsHeader) {
return qsHeader.findViewById(R.id.statusIcons);
}
-
- /** */
- @Provides
- @QSScope
- @Named(QS_FGS_MANAGER_FOOTER_VIEW)
- static View providesQSFgsManagerFooterView(
- @QSThemedContext LayoutInflater layoutInflater,
- FooterActionsView footerActionsView
- ) {
- return layoutInflater.inflate(R.layout.fgs_footer, footerActionsView, false);
- }
} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt
index 3e39c8ee62f1..6db3c9941b78 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt
@@ -35,35 +35,31 @@ import com.android.systemui.animation.Expandable
import com.android.systemui.common.ui.binder.IconViewBinder
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.people.ui.view.PeopleViewBinder.bind
-import com.android.systemui.qs.FooterActionsView
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsButtonViewModel
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsForegroundServicesButtonViewModel
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsSecurityButtonViewModel
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
+import kotlin.math.roundToInt
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
/** A ViewBinder for [FooterActionsViewBinder]. */
object FooterActionsViewBinder {
- /**
- * Create a [FooterActionsView] that can later be [bound][bind] to a [FooterActionsViewModel].
- */
+ /** Create a view that can later be [bound][bind] to a [FooterActionsViewModel]. */
@JvmStatic
- fun create(context: Context): FooterActionsView {
+ fun create(context: Context): LinearLayout {
return LayoutInflater.from(context).inflate(R.layout.footer_actions, /* root= */ null)
- as FooterActionsView
+ as LinearLayout
}
/** Bind [view] to [viewModel]. */
@JvmStatic
fun bind(
- view: FooterActionsView,
+ view: LinearLayout,
viewModel: FooterActionsViewModel,
qsVisibilityLifecycleOwner: LifecycleOwner,
) {
- // Remove all children of the FooterActionsView that are used by the old implementation.
- // TODO(b/242040009): Clean up the XML once the old implementation is removed.
- view.removeAllViews()
+ view.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_YES
// Add the views used by this new implementation.
val context = view.context
@@ -117,7 +113,11 @@ object FooterActionsViewBinder {
}
launch { viewModel.alpha.collect { view.alpha = it } }
- launch { viewModel.backgroundAlpha.collect { view.backgroundAlpha = it } }
+ launch {
+ viewModel.backgroundAlpha.collect {
+ view.background?.alpha = (it * 255).roundToInt()
+ }
+ }
}
// Listen for model changes only when QS are visible.
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
index 350d8b05dde5..28dd986c931b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
@@ -188,8 +188,6 @@ public class InternetTile extends QSTileImpl<SignalState> {
int mWifiSignalIconId;
@Nullable
String mSsid;
- boolean mActivityIn;
- boolean mActivityOut;
@Nullable
String mWifiSignalContentDescription;
boolean mIsTransient;
@@ -207,8 +205,6 @@ public class InternetTile extends QSTileImpl<SignalState> {
.append(",mConnected=").append(mConnected)
.append(",mWifiSignalIconId=").append(mWifiSignalIconId)
.append(",mSsid=").append(mSsid)
- .append(",mActivityIn=").append(mActivityIn)
- .append(",mActivityOut=").append(mActivityOut)
.append(",mWifiSignalContentDescription=").append(mWifiSignalContentDescription)
.append(",mIsTransient=").append(mIsTransient)
.append(",mNoDefaultNetwork=").append(mNoDefaultNetwork)
@@ -226,8 +222,6 @@ public class InternetTile extends QSTileImpl<SignalState> {
CharSequence mDataContentDescription;
int mMobileSignalIconId;
int mQsTypeIcon;
- boolean mActivityIn;
- boolean mActivityOut;
boolean mNoSim;
boolean mRoaming;
boolean mMultipleSubs;
@@ -243,8 +237,6 @@ public class InternetTile extends QSTileImpl<SignalState> {
.append(",mDataContentDescription=").append(mDataContentDescription)
.append(",mMobileSignalIconId=").append(mMobileSignalIconId)
.append(",mQsTypeIcon=").append(mQsTypeIcon)
- .append(",mActivityIn=").append(mActivityIn)
- .append(",mActivityOut=").append(mActivityOut)
.append(",mNoSim=").append(mNoSim)
.append(",mRoaming=").append(mRoaming)
.append(",mMultipleSubs=").append(mMultipleSubs)
@@ -275,8 +267,6 @@ public class InternetTile extends QSTileImpl<SignalState> {
mWifiInfo.mWifiSignalContentDescription = indicators.qsIcon.contentDescription;
mWifiInfo.mEnabled = indicators.enabled;
mWifiInfo.mSsid = indicators.description;
- mWifiInfo.mActivityIn = indicators.activityIn;
- mWifiInfo.mActivityOut = indicators.activityOut;
mWifiInfo.mIsTransient = indicators.isTransient;
mWifiInfo.mStatusLabel = indicators.statusLabel;
refreshState(mWifiInfo);
@@ -297,8 +287,6 @@ public class InternetTile extends QSTileImpl<SignalState> {
? indicators.typeContentDescriptionHtml : null;
mCellularInfo.mMobileSignalIconId = indicators.qsIcon.icon;
mCellularInfo.mQsTypeIcon = indicators.qsType;
- mCellularInfo.mActivityIn = indicators.activityIn;
- mCellularInfo.mActivityOut = indicators.activityOut;
mCellularInfo.mRoaming = indicators.roaming;
mCellularInfo.mMultipleSubs = mController.getNumberSubscriptions() > 1;
refreshState(mCellularInfo);
@@ -345,7 +333,14 @@ public class InternetTile extends QSTileImpl<SignalState> {
mCellularInfo.mAirplaneModeEnabled = icon.visible;
mWifiInfo.mAirplaneModeEnabled = icon.visible;
if (!mSignalCallback.mEthernetInfo.mConnected) {
- if (mWifiInfo.mEnabled && (mWifiInfo.mWifiSignalIconId > 0)
+ // Always use mWifiInfo to refresh the Internet Tile if airplane mode is enabled,
+ // because Internet Tile will show different information depending on whether WiFi
+ // is enabled or not.
+ if (mWifiInfo.mAirplaneModeEnabled) {
+ refreshState(mWifiInfo);
+ // If airplane mode is disabled, we will use mWifiInfo to refresh the Internet Tile
+ // if WiFi is currently connected to avoid any icon flickering.
+ } else if (mWifiInfo.mEnabled && (mWifiInfo.mWifiSignalIconId > 0)
&& (mWifiInfo.mSsid != null)) {
refreshState(mWifiInfo);
} else {
@@ -428,8 +423,6 @@ public class InternetTile extends QSTileImpl<SignalState> {
state.state = Tile.STATE_ACTIVE;
state.dualTarget = true;
state.value = cb.mEnabled;
- state.activityIn = cb.mEnabled && cb.mActivityIn;
- state.activityOut = cb.mEnabled && cb.mActivityOut;
final StringBuffer minimalContentDescription = new StringBuffer();
final StringBuffer minimalStateDescription = new StringBuffer();
final Resources r = mContext.getResources();
@@ -503,8 +496,6 @@ public class InternetTile extends QSTileImpl<SignalState> {
boolean mobileDataEnabled = mDataController.isMobileDataSupported()
&& mDataController.isMobileDataEnabled();
state.value = mobileDataEnabled;
- state.activityIn = mobileDataEnabled && cb.mActivityIn;
- state.activityOut = mobileDataEnabled && cb.mActivityOut;
state.expandedAccessibilityClassName = Switch.class.getName();
if (cb.mAirplaneModeEnabled && cb.mQsTypeIcon != TelephonyIcons.ICON_CWF) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
index 7130294deccb..a6c7781d891c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
@@ -27,6 +27,7 @@ import android.service.quicksettings.Tile;
import android.view.View;
import android.widget.Switch;
+import androidx.annotation.MainThread;
import androidx.annotation.Nullable;
import com.android.internal.logging.MetricsLogger;
@@ -91,11 +92,13 @@ public class WorkModeTile extends QSTileImpl<BooleanState> implements
}
@Override
+ @MainThread
public void onManagedProfileChanged() {
refreshState(mProfileController.isWorkModeEnabled());
}
@Override
+ @MainThread
public void onManagedProfileRemoved() {
mHost.removeTile(getTileSpec());
mHost.unmarkTileAsAutoAdded(getTileSpec());
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 547b496beaff..00d129ae70ca 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -565,13 +565,25 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
statusBarWinController.registerCallback(mStatusBarWindowCallback);
mScreenshotHelper = new ScreenshotHelper(context);
- // Listen for tracing state changes
commandQueue.addCallback(new CommandQueue.Callbacks() {
+
+ // Listen for tracing state changes
@Override
public void onTracingStateChanged(boolean enabled) {
mSysUiState.setFlag(SYSUI_STATE_TRACING_ENABLED, enabled)
.commitUpdate(mContext.getDisplayId());
}
+
+ @Override
+ public void enterStageSplitFromRunningApp(boolean leftOrTop) {
+ if (mOverviewProxy != null) {
+ try {
+ mOverviewProxy.enterStageSplitFromRunningApp(leftOrTop);
+ } catch (RemoteException e) {
+ Log.w(TAG_OPS, "Unable to enter stage split from the current running app");
+ }
+ }
+ }
});
mCommandQueue = commandQueue;
diff --git a/packages/SystemUI/src/com/android/systemui/security/data/model/SecurityModel.kt b/packages/SystemUI/src/com/android/systemui/security/data/model/SecurityModel.kt
index 50af260684f8..1cf5a8fe5f97 100644
--- a/packages/SystemUI/src/com/android/systemui/security/data/model/SecurityModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/security/data/model/SecurityModel.kt
@@ -17,6 +17,7 @@
package com.android.systemui.security.data.model
import android.graphics.drawable.Drawable
+import androidx.annotation.VisibleForTesting
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.statusbar.policy.SecurityController
import kotlinx.coroutines.CoroutineDispatcher
@@ -55,8 +56,8 @@ data class SecurityModel(
* Important: This method should be called from a background thread as this will do a lot of
* binder calls.
*/
- // TODO(b/242040009): Remove this.
@JvmStatic
+ @VisibleForTesting
fun create(securityController: SecurityController): SecurityModel {
val deviceAdminInfo =
if (securityController.isParentalControlsEnabled) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
index 5e47d6dd5b76..b511b5463cbf 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
@@ -46,7 +46,6 @@ import com.android.systemui.qs.carrier.QSCarrierGroup
import com.android.systemui.qs.carrier.QSCarrierGroupController
import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.HEADER_TRANSITION_ID
import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.LARGE_SCREEN_HEADER_CONSTRAINT
-import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.LARGE_SCREEN_HEADER_TRANSITION_ID
import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.QQS_HEADER_CONSTRAINT
import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.QS_HEADER_CONSTRAINT
import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
@@ -73,11 +72,9 @@ import javax.inject.Named
* expansion of the headers in small screen portrait.
*
* [header] will be a [MotionLayout] if [Flags.COMBINED_QS_HEADERS] is enabled. In this case, the
- * [MotionLayout] has 2 transitions:
+ * [MotionLayout] has one transitions:
* * [HEADER_TRANSITION_ID]: [QQS_HEADER_CONSTRAINT] <-> [QS_HEADER_CONSTRAINT] for portrait
* handheld device configuration.
- * * [LARGE_SCREEN_HEADER_TRANSITION_ID]: [LARGE_SCREEN_HEADER_CONSTRAINT] (to itself) for all
- * other configurations
*/
@CentralSurfacesScope
class LargeScreenShadeHeaderController @Inject constructor(
@@ -104,8 +101,6 @@ class LargeScreenShadeHeaderController @Inject constructor(
@VisibleForTesting
internal val HEADER_TRANSITION_ID = R.id.header_transition
@VisibleForTesting
- internal val LARGE_SCREEN_HEADER_TRANSITION_ID = R.id.large_screen_header_transition
- @VisibleForTesting
internal val QQS_HEADER_CONSTRAINT = R.id.qqs_header_constraint
@VisibleForTesting
internal val QS_HEADER_CONSTRAINT = R.id.qs_header_constraint
@@ -120,10 +115,6 @@ class LargeScreenShadeHeaderController @Inject constructor(
}
}
- init {
- loadConstraints()
- }
-
private val combinedHeaders = featureFlags.isEnabled(Flags.COMBINED_QS_HEADERS)
private lateinit var iconManager: StatusBarIconController.TintedIconManager
@@ -345,11 +336,11 @@ class LargeScreenShadeHeaderController @Inject constructor(
if (header is MotionLayout) {
// Use resources.getXml instead of passing the resource id due to bug b/205018300
header.getConstraintSet(QQS_HEADER_CONSTRAINT)
- .load(context, resources.getXml(R.xml.qqs_header))
+ .load(context, resources.getXml(R.xml.qqs_header))
header.getConstraintSet(QS_HEADER_CONSTRAINT)
- .load(context, resources.getXml(R.xml.qs_header))
+ .load(context, resources.getXml(R.xml.qs_header))
header.getConstraintSet(LARGE_SCREEN_HEADER_CONSTRAINT)
- .load(context, resources.getXml(R.xml.large_screen_shade_header))
+ .load(context, resources.getXml(R.xml.large_screen_shade_header))
}
}
@@ -438,7 +429,6 @@ class LargeScreenShadeHeaderController @Inject constructor(
}
header as MotionLayout
if (largeScreenActive) {
- header.setTransition(LARGE_SCREEN_HEADER_TRANSITION_ID)
header.getConstraintSet(LARGE_SCREEN_HEADER_CONSTRAINT).applyTo(header)
} else {
header.setTransition(HEADER_TRANSITION_ID)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 60376f4deec8..507dec67b11a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -200,7 +200,6 @@ import com.android.systemui.statusbar.phone.KeyguardStatusBarView;
import com.android.systemui.statusbar.phone.KeyguardStatusBarViewController;
import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
import com.android.systemui.statusbar.phone.LockscreenGestureLogger.LockscreenUiEvent;
-import com.android.systemui.statusbar.phone.PhoneStatusBarView;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
import com.android.systemui.statusbar.phone.ScrimController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
@@ -466,7 +465,11 @@ public final class NotificationPanelViewController implements Dumpable {
private boolean mQsTouchAboveFalsingThreshold;
private int mQsFalsingThreshold;
- /** Indicates drag starting height when swiping down or up on heads-up notifications */
+ /**
+ * Indicates drag starting height when swiping down or up on heads-up notifications.
+ * This usually serves as a threshold from when shade expansion should really start. Otherwise
+ * this value would be height of shade and it will be immediately expanded to some extent.
+ */
private int mHeadsUpStartHeight;
private HeadsUpTouchHelper mHeadsUpTouchHelper;
private boolean mListenForHeadsUp;
@@ -916,6 +919,7 @@ public final class NotificationPanelViewController implements Dumpable {
mQsFrameTranslateController = qsFrameTranslateController;
updateUserSwitcherFlags();
mKeyguardBottomAreaViewModel = keyguardBottomAreaViewModel;
+ mKeyguardBottomAreaInteractor = keyguardBottomAreaInteractor;
onFinishInflate();
keyguardUnlockAnimationController.addKeyguardUnlockAnimationListener(
new KeyguardUnlockAnimationController.KeyguardUnlockAnimationListener() {
@@ -933,7 +937,6 @@ public final class NotificationPanelViewController implements Dumpable {
unlockAnimationStarted(playingCannedAnimation, isWakeAndUnlock, startDelay);
}
});
- mKeyguardBottomAreaInteractor = keyguardBottomAreaInteractor;
dumpManager.registerDumpable(this);
}
@@ -1109,6 +1112,7 @@ public final class NotificationPanelViewController implements Dumpable {
mKeyguardStatusViewComponentFactory.build(keyguardStatusView);
mKeyguardStatusViewController = statusViewComponent.getKeyguardStatusViewController();
mKeyguardStatusViewController.init();
+ updateClockAppearance();
if (mKeyguardUserSwitcherController != null) {
// Try to close the switcher so that callbacks are triggered if necessary.
@@ -3412,9 +3416,12 @@ public final class NotificationPanelViewController implements Dumpable {
&& mQsExpansionAnimator == null && !mQsExpansionFromOverscroll;
boolean goingBetweenClosedShadeAndExpandedQs =
mQsExpandImmediate || collapsingShadeFromExpandedQs;
- // we don't want to update QS expansion when HUN is visible because then the whole shade is
- // initially hidden, even though it has non-zero height
- if (goingBetweenClosedShadeAndExpandedQs && !mHeadsUpManager.isTrackingHeadsUp()) {
+ // in split shade we react when HUN is visible only if shade height is over HUN start
+ // height - which means user is swiping down. Otherwise shade QS will either not show at all
+ // with HUN movement or it will blink when touching HUN initially
+ boolean qsShouldExpandWithHeadsUp = !mSplitShadeEnabled
+ || (!mHeadsUpManager.isTrackingHeadsUp() || expandedHeight > mHeadsUpStartHeight);
+ if (goingBetweenClosedShadeAndExpandedQs && qsShouldExpandWithHeadsUp) {
float qsExpansionFraction;
if (mSplitShadeEnabled) {
qsExpansionFraction = 1;
@@ -4587,55 +4594,6 @@ public final class NotificationPanelViewController implements Dumpable {
return new TouchHandler();
}
- private final PhoneStatusBarView.TouchEventHandler mStatusBarViewTouchEventHandler =
- new PhoneStatusBarView.TouchEventHandler() {
- @Override
- public void onInterceptTouchEvent(MotionEvent event) {
- mCentralSurfaces.onTouchEvent(event);
- }
-
- @Override
- public boolean handleTouchEvent(MotionEvent event) {
- mCentralSurfaces.onTouchEvent(event);
-
- // TODO(b/202981994): Move the touch debugging in this method to a central
- // location. (Right now, it's split between CentralSurfaces and here.)
-
- // If panels aren't enabled, ignore the gesture and don't pass it down to the
- // panel view.
- if (!mCommandQueue.panelsEnabled()) {
- if (event.getAction() == MotionEvent.ACTION_DOWN) {
- Log.v(
- TAG,
- String.format(
- "onTouchForwardedFromStatusBar: "
- + "panel disabled, ignoring touch at (%d,%d)",
- (int) event.getX(),
- (int) event.getY()
- )
- );
- }
- return false;
- }
-
- if (event.getAction() == MotionEvent.ACTION_DOWN) {
- // If the view that would receive the touch is disabled, just have status
- // bar eat the gesture.
- if (!mView.isEnabled()) {
- mShadeLog.logMotionEvent(event,
- "onTouchForwardedFromStatusBar: panel view disabled");
- return true;
- }
- if (isFullyCollapsed() && event.getY() < 1f) {
- // b/235889526 Eat events on the top edge of the phone when collapsed
- mShadeLog.logMotionEvent(event, "top edge touch ignored");
- return true;
- }
- }
- return mView.dispatchTouchEvent(event);
- }
- };
-
public NotificationStackScrollLayoutController getNotificationStackScrollLayoutController() {
return mNotificationStackScrollLayoutController;
}
@@ -5238,6 +5196,11 @@ public final class NotificationPanelViewController implements Dumpable {
}
/** */
+ public boolean sendTouchEventToView(MotionEvent event) {
+ return mView.dispatchTouchEvent(event);
+ }
+
+ /** */
public void requestLayoutOnView() {
mView.requestLayout();
}
@@ -5247,6 +5210,11 @@ public final class NotificationPanelViewController implements Dumpable {
ViewGroupFadeHelper.reset(mView);
}
+ /** */
+ public boolean isViewEnabled() {
+ return mView.isEnabled();
+ }
+
private void beginJankMonitoring() {
if (mInteractionJankMonitor == null) {
return;
@@ -5796,11 +5764,6 @@ public final class NotificationPanelViewController implements Dumpable {
mCurrentPanelState = state;
}
- /** Returns the handler that the status bar should forward touches to. */
- public PhoneStatusBarView.TouchEventHandler getStatusBarTouchEventHandler() {
- return mStatusBarViewTouchEventHandler;
- }
-
@VisibleForTesting
StatusBarStateController getStatusBarStateController() {
return mStatusBarStateController;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 1dd3a967faaf..590a04a3e264 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -164,6 +164,8 @@ public class CommandQueue extends IStatusBar.Stub implements
private static final int MSG_UNREGISTER_NEARBY_MEDIA_DEVICE_PROVIDER = 67 << MSG_SHIFT;
private static final int MSG_TILE_SERVICE_REQUEST_LISTENING_STATE = 68 << MSG_SHIFT;
private static final int MSG_SHOW_REAR_DISPLAY_DIALOG = 69 << MSG_SHIFT;
+ private static final int MSG_GO_TO_FULLSCREEN_FROM_SPLIT = 70 << MSG_SHIFT;
+ private static final int MSG_ENTER_STAGE_SPLIT_FROM_RUNNING_APP = 71 << MSG_SHIFT;
public static final int FLAG_EXCLUDE_NONE = 0;
public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0;
@@ -478,6 +480,16 @@ public class CommandQueue extends IStatusBar.Stub implements
* @see IStatusBar#showRearDisplayDialog
*/
default void showRearDisplayDialog(int currentBaseState) {}
+
+ /**
+ * @see IStatusBar#goToFullscreenFromSplit
+ */
+ default void goToFullscreenFromSplit() {}
+
+ /**
+ * @see IStatusBar#enterStageSplitFromRunningApp
+ */
+ default void enterStageSplitFromRunningApp(boolean leftOrTop) {}
}
public CommandQueue(Context context) {
@@ -1239,6 +1251,14 @@ public class CommandQueue extends IStatusBar.Stub implements
}
@Override
+ public void enterStageSplitFromRunningApp(boolean leftOrTop) {
+ synchronized (mLock) {
+ mHandler.obtainMessage(MSG_ENTER_STAGE_SPLIT_FROM_RUNNING_APP,
+ leftOrTop).sendToTarget();
+ }
+ }
+
+ @Override
public void requestAddTile(
@NonNull ComponentName componentName,
@NonNull CharSequence appName,
@@ -1299,6 +1319,11 @@ public class CommandQueue extends IStatusBar.Stub implements
.sendToTarget();
}
+ @Override
+ public void goToFullscreenFromSplit() {
+ mHandler.obtainMessage(MSG_GO_TO_FULLSCREEN_FROM_SPLIT).sendToTarget();
+ }
+
private final class H extends Handler {
private H(Looper l) {
super(l);
@@ -1738,6 +1763,17 @@ public class CommandQueue extends IStatusBar.Stub implements
for (int i = 0; i < mCallbacks.size(); i++) {
mCallbacks.get(i).showRearDisplayDialog((Integer) msg.obj);
}
+ break;
+ case MSG_GO_TO_FULLSCREEN_FROM_SPLIT:
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ mCallbacks.get(i).goToFullscreenFromSplit();
+ }
+ break;
+ case MSG_ENTER_STAGE_SPLIT_FROM_RUNNING_APP:
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ mCallbacks.get(i).enterStageSplitFromRunningApp((Boolean) msg.obj);
+ }
+ break;
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java b/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java
index 3d161d9bfa75..24c66eb5d442 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java
@@ -17,9 +17,12 @@
package com.android.systemui.statusbar;
import android.annotation.ColorInt;
+import android.annotation.DrawableRes;
import android.annotation.StringRes;
import android.content.Context;
+import android.content.res.ColorStateList;
import android.content.res.Configuration;
+import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.View;
import android.widget.TextView;
@@ -33,16 +36,30 @@ import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
public class EmptyShadeView extends StackScrollerDecorView {
private TextView mEmptyText;
+ private TextView mEmptyFooterText;
+
private @StringRes int mText = R.string.empty_shade_text;
+ private @DrawableRes int mFooterIcon = R.drawable.ic_friction_lock_closed;
+ private @StringRes int mFooterText = R.string.unlock_to_see_notif_text;
+ private @Visibility int mFooterVisibility = View.GONE;
+ private int mSize;
+
public EmptyShadeView(Context context, AttributeSet attrs) {
super(context, attrs);
+ mSize = getResources().getDimensionPixelSize(
+ R.dimen.notifications_unseen_footer_icon_size);
}
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
+ mSize = getResources().getDimensionPixelSize(
+ R.dimen.notifications_unseen_footer_icon_size);
mEmptyText.setText(mText);
+ mEmptyFooterText.setVisibility(mFooterVisibility);
+ setFooterText(mFooterText);
+ setFooterIcon(mFooterIcon);
}
@Override
@@ -52,11 +69,13 @@ public class EmptyShadeView extends StackScrollerDecorView {
@Override
protected View findSecondaryView() {
- return null;
+ return findViewById(R.id.no_notifications_footer);
}
public void setTextColor(@ColorInt int color) {
mEmptyText.setTextColor(color);
+ mEmptyFooterText.setTextColor(color);
+ mEmptyFooterText.setCompoundDrawableTintList(ColorStateList.valueOf(color));
}
public void setText(@StringRes int text) {
@@ -64,14 +83,53 @@ public class EmptyShadeView extends StackScrollerDecorView {
mEmptyText.setText(mText);
}
+ public void setFooterVisibility(@Visibility int visibility) {
+ mFooterVisibility = visibility;
+ setSecondaryVisible(visibility == View.VISIBLE, false);
+ }
+
+ public void setFooterText(@StringRes int text) {
+ mFooterText = text;
+ if (text != 0) {
+ mEmptyFooterText.setText(mFooterText);
+ } else {
+ mEmptyFooterText.setText(null);
+ }
+ }
+
+ public void setFooterIcon(@DrawableRes int icon) {
+ mFooterIcon = icon;
+ Drawable drawable;
+ if (icon == 0) {
+ drawable = null;
+ } else {
+ drawable = getResources().getDrawable(icon);
+ drawable.setBounds(0, 0, mSize, mSize);
+ }
+ mEmptyFooterText.setCompoundDrawablesRelative(drawable, null, null, null);
+ }
+
+ @StringRes
public int getTextResource() {
return mText;
}
+ @StringRes
+ public int getFooterTextResource() {
+ return mFooterText;
+ }
+
+ @DrawableRes
+ public int getFooterIconResource() {
+ return mFooterIcon;
+ }
+
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mEmptyText = (TextView) findContentView();
+ mEmptyFooterText = (TextView) findSecondaryView();
+ mEmptyFooterText.setCompoundDrawableTintList(mEmptyFooterText.getTextColors());
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index d7eddf53dea5..56c34a0b3665 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -39,6 +39,7 @@ import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.animation.ShadeInterpolation;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
+import com.android.systemui.statusbar.notification.LegacySourceType;
import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.SourceType;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
@@ -66,6 +67,8 @@ public class NotificationShelf extends ActivatableNotificationView implements
// the next icon has translated out of the way, to avoid overlapping.
private static final Interpolator ICON_ALPHA_INTERPOLATOR =
new PathInterpolator(0.6f, 0f, 0.6f, 0f);
+ private static final SourceType BASE_VALUE = SourceType.from("BaseValue");
+ private static final SourceType SHELF_SCROLL = SourceType.from("ShelfScroll");
private NotificationIconContainer mShelfIcons;
private int[] mTmp = new int[2];
@@ -112,19 +115,24 @@ public class NotificationShelf extends ActivatableNotificationView implements
setClipChildren(false);
setClipToPadding(false);
mShelfIcons.setIsStaticLayout(false);
- requestBottomRoundness(1.0f, /* animate = */ false, SourceType.DefaultValue);
- requestTopRoundness(1f, false, SourceType.DefaultValue);
+ requestRoundness(/* top = */ 1f, /* bottom = */ 1f, BASE_VALUE, /* animate = */ false);
- // Setting this to first in section to get the clipping to the top roundness correct. This
- // value determines the way we are clipping to the top roundness of the overall shade
- setFirstInSection(true);
+ if (!mUseRoundnessSourceTypes) {
+ // Setting this to first in section to get the clipping to the top roundness correct.
+ // This value determines the way we are clipping to the top roundness of the overall
+ // shade
+ setFirstInSection(true);
+ }
updateResources();
}
public void bind(AmbientState ambientState,
- NotificationStackScrollLayoutController hostLayoutController) {
+ NotificationStackScrollLayoutController hostLayoutController) {
mAmbientState = ambientState;
mHostLayoutController = hostLayoutController;
+ hostLayoutController.setOnNotificationRemovedListener((child, isTransferInProgress) -> {
+ child.requestRoundnessReset(SHELF_SCROLL);
+ });
}
private void updateResources() {
@@ -185,9 +193,11 @@ public class NotificationShelf extends ActivatableNotificationView implements
+ " indexOfFirstViewInShelf=" + mIndexOfFirstViewInShelf + ')';
}
- /** Update the state of the shelf. */
+ /**
+ * Update the state of the shelf.
+ */
public void updateState(StackScrollAlgorithm.StackScrollAlgorithmState algorithmState,
- AmbientState ambientState) {
+ AmbientState ambientState) {
ExpandableView lastView = ambientState.getLastVisibleBackgroundChild();
ShelfState viewState = (ShelfState) getViewState();
if (mShowNotificationShelf && lastView != null) {
@@ -246,7 +256,7 @@ public class NotificationShelf extends ActivatableNotificationView implements
/**
* @param fractionToShade Fraction of lockscreen to shade transition
- * @param shortestWidth Shortest width to use for lockscreen shelf
+ * @param shortestWidth Shortest width to use for lockscreen shelf
*/
@VisibleForTesting
public void updateActualWidth(float fractionToShade, float shortestWidth) {
@@ -281,9 +291,9 @@ public class NotificationShelf extends ActivatableNotificationView implements
/**
* @param localX Click x from left of screen
- * @param slop Margin of error within which we count x for valid click
- * @param left Left of shelf, from left of screen
- * @param right Right of shelf, from left of screen
+ * @param slop Margin of error within which we count x for valid click
+ * @param left Left of shelf, from left of screen
+ * @param right Right of shelf, from left of screen
* @return Whether click x was in view
*/
@VisibleForTesting
@@ -293,8 +303,8 @@ public class NotificationShelf extends ActivatableNotificationView implements
/**
* @param localY Click y from top of shelf
- * @param slop Margin of error within which we count y for valid click
- * @param top Top of shelf
+ * @param slop Margin of error within which we count y for valid click
+ * @param top Top of shelf
* @param bottom Height of shelf
* @return Whether click y was in view
*/
@@ -306,7 +316,7 @@ public class NotificationShelf extends ActivatableNotificationView implements
/**
* @param localX Click x
* @param localY Click y
- * @param slop Margin of error for valid click
+ * @param slop Margin of error for valid click
* @return Whether this click was on the visible (non-clipped) part of the shelf
*/
@Override
@@ -478,13 +488,15 @@ public class NotificationShelf extends ActivatableNotificationView implements
}
}
- private void updateCornerRoundnessOnScroll(ActivatableNotificationView anv, float viewStart,
+ private void updateCornerRoundnessOnScroll(
+ ActivatableNotificationView anv,
+ float viewStart,
float shelfStart) {
final boolean isUnlockedHeadsUp = !mAmbientState.isOnKeyguard()
&& !mAmbientState.isShadeExpanded()
&& anv instanceof ExpandableNotificationRow
- && ((ExpandableNotificationRow) anv).isHeadsUp();
+ && anv.isHeadsUp();
final boolean isHunGoingToShade = mAmbientState.isShadeExpanded()
&& anv == mAmbientState.getTrackedHeadsUpRow();
@@ -506,41 +518,40 @@ public class NotificationShelf extends ActivatableNotificationView implements
* mAmbientState.getExpansionFraction();
final float cornerAnimationTop = shelfStart - cornerAnimationDistance;
- if (viewEnd >= cornerAnimationTop) {
- // Round bottom corners within animation bounds
- final float changeFraction = MathUtils.saturate(
- (viewEnd - cornerAnimationTop) / cornerAnimationDistance);
- anv.requestBottomRoundness(
- /* value = */ anv.isLastInSection() ? 1f : changeFraction,
- /* animate = */ false,
- SourceType.OnScroll);
-
- } else if (viewEnd < cornerAnimationTop) {
- // Fast scroll skips frames and leaves corners with unfinished rounding.
- // Reset top and bottom corners outside of animation bounds.
- anv.requestBottomRoundness(
- /* value = */ anv.isLastInSection() ? 1f : 0f,
- /* animate = */ false,
- SourceType.OnScroll);
+ final SourceType sourceType;
+ if (mUseRoundnessSourceTypes) {
+ sourceType = SHELF_SCROLL;
+ } else {
+ sourceType = LegacySourceType.OnScroll;
}
- if (viewStart >= cornerAnimationTop) {
+ final float topValue;
+ if (!mUseRoundnessSourceTypes && anv.isFirstInSection()) {
+ topValue = 1f;
+ } else if (viewStart >= cornerAnimationTop) {
// Round top corners within animation bounds
- final float changeFraction = MathUtils.saturate(
+ topValue = MathUtils.saturate(
(viewStart - cornerAnimationTop) / cornerAnimationDistance);
- anv.requestTopRoundness(
- /* value = */ anv.isFirstInSection() ? 1f : changeFraction,
- /* animate = */ false,
- SourceType.OnScroll);
+ } else {
+ // Fast scroll skips frames and leaves corners with unfinished rounding.
+ // Reset top and bottom corners outside of animation bounds.
+ topValue = 0f;
+ }
+ anv.requestTopRoundness(topValue, sourceType, /* animate = */ false);
- } else if (viewStart < cornerAnimationTop) {
+ final float bottomValue;
+ if (!mUseRoundnessSourceTypes && anv.isLastInSection()) {
+ bottomValue = 1f;
+ } else if (viewEnd >= cornerAnimationTop) {
+ // Round bottom corners within animation bounds
+ bottomValue = MathUtils.saturate(
+ (viewEnd - cornerAnimationTop) / cornerAnimationDistance);
+ } else {
// Fast scroll skips frames and leaves corners with unfinished rounding.
// Reset top and bottom corners outside of animation bounds.
- anv.requestTopRoundness(
- /* value = */ anv.isFirstInSection() ? 1f : 0f,
- /* animate = */ false,
- SourceType.OnScroll);
+ bottomValue = 0f;
}
+ anv.requestBottomRoundness(bottomValue, sourceType, /* animate = */ false);
}
/**
@@ -626,10 +637,11 @@ public class NotificationShelf extends ActivatableNotificationView implements
/**
* Update the clipping of this view.
+ *
* @return the amount that our own top should be clipped
*/
private int updateNotificationClipHeight(ExpandableView view,
- float notificationClipEnd, int childIndex) {
+ float notificationClipEnd, int childIndex) {
float viewEnd = view.getTranslationY() + view.getActualHeight();
boolean isPinned = (view.isPinned() || view.isHeadsUpAnimatingAway())
&& !mAmbientState.isDozingAndNotPulsing(view);
@@ -657,7 +669,7 @@ public class NotificationShelf extends ActivatableNotificationView implements
@Override
public void setFakeShadowIntensity(float shadowIntensity, float outlineAlpha, int shadowYEnd,
- int outlineTranslation) {
+ int outlineTranslation) {
if (!mHasItemsInStableShelf) {
shadowIntensity = 0.0f;
}
@@ -665,18 +677,24 @@ public class NotificationShelf extends ActivatableNotificationView implements
}
/**
- * @param i Index of the view in the host layout.
- * @param view The current ExpandableView.
- * @param scrollingFast Whether we are scrolling fast.
+ * @param i Index of the view in the host layout.
+ * @param view The current ExpandableView.
+ * @param scrollingFast Whether we are scrolling fast.
* @param expandingAnimated Whether we are expanding a notification.
- * @param isLastChild Whether this is the last view.
- * @param shelfClipStart The point at which notifications start getting clipped by the shelf.
+ * @param isLastChild Whether this is the last view.
+ * @param shelfClipStart The point at which notifications start getting clipped by the shelf.
* @return The amount how much this notification is in the shelf.
- * 0f is not in shelf. 1f is completely in shelf.
+ * 0f is not in shelf. 1f is completely in shelf.
*/
@VisibleForTesting
- public float getAmountInShelf(int i, ExpandableView view, boolean scrollingFast,
- boolean expandingAnimated, boolean isLastChild, float shelfClipStart) {
+ public float getAmountInShelf(
+ int i,
+ ExpandableView view,
+ boolean scrollingFast,
+ boolean expandingAnimated,
+ boolean isLastChild,
+ float shelfClipStart
+ ) {
// Let's calculate how much the view is in the shelf
float viewStart = view.getTranslationY();
@@ -755,8 +773,13 @@ public class NotificationShelf extends ActivatableNotificationView implements
return start;
}
- private void updateIconPositioning(ExpandableView view, float iconTransitionAmount,
- boolean scrollingFast, boolean expandingAnimated, boolean isLastChild) {
+ private void updateIconPositioning(
+ ExpandableView view,
+ float iconTransitionAmount,
+ boolean scrollingFast,
+ boolean expandingAnimated,
+ boolean isLastChild
+ ) {
StatusBarIconView icon = view.getShelfIcon();
NotificationIconContainer.IconState iconState = getIconState(icon);
if (iconState == null) {
@@ -817,7 +840,7 @@ public class NotificationShelf extends ActivatableNotificationView implements
|| row.showingPulsing()
|| row.getTranslationZ() > mAmbientState.getBaseZHeight();
- iconState.iconAppearAmount = iconState.hidden? 0f : transitionAmount;
+ iconState.iconAppearAmount = iconState.hidden ? 0f : transitionAmount;
// Fade in icons at shelf start
// This is important for conversation icons, which are badged and need x reset
@@ -847,7 +870,7 @@ public class NotificationShelf extends ActivatableNotificationView implements
}
private float getFullyClosedTranslation() {
- return - (getIntrinsicHeight() - mStatusBarHeight) / 2;
+ return -(getIntrinsicHeight() - mStatusBarHeight) / 2;
}
@Override
@@ -904,7 +927,7 @@ public class NotificationShelf extends ActivatableNotificationView implements
/**
* @return whether the shelf has any icons in it when a potential animation has finished, i.e
- * if the current state would be applied right now
+ * if the current state would be applied right now
*/
public boolean hasItemsInStableShelf() {
return mHasItemsInStableShelf;
@@ -962,7 +985,7 @@ public class NotificationShelf extends ActivatableNotificationView implements
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
- int oldTop, int oldRight, int oldBottom) {
+ int oldTop, int oldRight, int oldBottom) {
updateRelativeOffset();
}
@@ -981,12 +1004,11 @@ public class NotificationShelf extends ActivatableNotificationView implements
/**
* This method resets the OnScroll roundness of a view to 0f
- *
+ * <p>
* Note: This should be the only class that handles roundness {@code SourceType.OnScroll}
*/
- public static void resetOnScrollRoundness(ExpandableView expandableView) {
- expandableView.requestTopRoundness(0f, false, SourceType.OnScroll);
- expandableView.requestBottomRoundness(0f, false, SourceType.OnScroll);
+ public static void resetLegacyOnScrollRoundness(ExpandableView expandableView) {
+ expandableView.requestRoundnessReset(LegacySourceType.OnScroll);
}
public class ShelfState extends ExpandableViewState {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.java
index 3b1fa1779c17..bb84c758d87d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.java
@@ -18,6 +18,8 @@ package com.android.systemui.statusbar;
import android.view.View;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationViewController;
import com.android.systemui.statusbar.notification.row.dagger.NotificationRowScope;
@@ -42,14 +44,17 @@ public class NotificationShelfController {
private AmbientState mAmbientState;
@Inject
- public NotificationShelfController(NotificationShelf notificationShelf,
+ public NotificationShelfController(
+ NotificationShelf notificationShelf,
ActivatableNotificationViewController activatableNotificationViewController,
KeyguardBypassController keyguardBypassController,
- SysuiStatusBarStateController statusBarStateController) {
+ SysuiStatusBarStateController statusBarStateController,
+ FeatureFlags featureFlags) {
mView = notificationShelf;
mActivatableNotificationViewController = activatableNotificationViewController;
mKeyguardBypassController = keyguardBypassController;
mStatusBarStateController = statusBarStateController;
+ mView.useRoundnessSourceTypes(featureFlags.isEnabled(Flags.USE_ROUNDNESS_SOURCETYPES));
mOnAttachStateChangeListener = new View.OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View v) {
@@ -88,7 +93,7 @@ public class NotificationShelfController {
public @View.Visibility int getVisibility() {
return mView.getVisibility();
- };
+ }
public void setCollapsedIcons(NotificationIconContainer notificationIcons) {
mView.setCollapsedIcons(notificationIcons);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
index 99ff06a247bc..336356eae364 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
@@ -68,7 +68,7 @@ import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.LongRunning;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.demomode.DemoMode;
import com.android.systemui.demomode.DemoModeController;
@@ -78,6 +78,7 @@ import com.android.systemui.plugins.log.LogBuffer;
import com.android.systemui.plugins.log.LogLevel;
import com.android.systemui.qs.tiles.dialog.InternetDialogFactory;
import com.android.systemui.settings.UserTracker;
+import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.DataSaverController;
import com.android.systemui.statusbar.policy.DataSaverControllerImpl;
@@ -192,6 +193,7 @@ public class NetworkControllerImpl extends BroadcastReceiver
private final Executor mBgExecutor;
// Handler that all callbacks are made on.
private final CallbackHandler mCallbackHandler;
+ private final StatusBarPipelineFlags mStatusBarPipelineFlags;
private int mEmergencySource;
private boolean mIsEmergency;
@@ -223,12 +225,15 @@ public class NetworkControllerImpl extends BroadcastReceiver
/**
* Construct this controller object and register for updates.
+ *
+ * {@code @LongRunning} looper and bgExecutor instead {@code @Background} ones are used to
+ * address the b/246456655. This can be reverted after b/240663726 is fixed.
*/
@Inject
public NetworkControllerImpl(
Context context,
- @Background Looper bgLooper,
- @Background Executor bgExecutor,
+ @LongRunning Looper bgLooper,
+ @LongRunning Executor bgExecutor,
SubscriptionManager subscriptionManager,
CallbackHandler callbackHandler,
DeviceProvisionedController deviceProvisionedController,
@@ -239,6 +244,7 @@ public class NetworkControllerImpl extends BroadcastReceiver
TelephonyListenerManager telephonyListenerManager,
@Nullable WifiManager wifiManager,
AccessPointControllerImpl accessPointController,
+ StatusBarPipelineFlags statusBarPipelineFlags,
DemoModeController demoModeController,
CarrierConfigTracker carrierConfigTracker,
WifiStatusTrackerFactory trackerFactory,
@@ -257,6 +263,7 @@ public class NetworkControllerImpl extends BroadcastReceiver
bgExecutor,
callbackHandler,
accessPointController,
+ statusBarPipelineFlags,
new DataUsageController(context),
new SubscriptionDefaults(),
deviceProvisionedController,
@@ -284,6 +291,7 @@ public class NetworkControllerImpl extends BroadcastReceiver
Executor bgExecutor,
CallbackHandler callbackHandler,
AccessPointControllerImpl accessPointController,
+ StatusBarPipelineFlags statusBarPipelineFlags,
DataUsageController dataUsageController,
SubscriptionDefaults defaultsHandler,
DeviceProvisionedController deviceProvisionedController,
@@ -305,6 +313,7 @@ public class NetworkControllerImpl extends BroadcastReceiver
mBgLooper = bgLooper;
mBgExecutor = bgExecutor;
mCallbackHandler = callbackHandler;
+ mStatusBarPipelineFlags = statusBarPipelineFlags;
mDataSaverController = new DataSaverControllerImpl(context);
mBroadcastDispatcher = broadcastDispatcher;
mMobileFactory = mobileFactory;
@@ -1330,7 +1339,7 @@ public class NetworkControllerImpl extends BroadcastReceiver
mWifiSignalController.notifyListeners();
}
String sims = args.getString("sims");
- if (sims != null) {
+ if (sims != null && !mStatusBarPipelineFlags.useNewMobileIcons()) {
int num = MathUtils.constrain(Integer.parseInt(sims), 1, 8);
List<SubscriptionInfo> subs = new ArrayList<>();
if (num != mMobileSignalControllers.size()) {
@@ -1353,7 +1362,7 @@ public class NetworkControllerImpl extends BroadcastReceiver
mCallbackHandler.setNoSims(mHasNoSubs, mSimDetected);
}
String mobile = args.getString("mobile");
- if (mobile != null) {
+ if (mobile != null && !mStatusBarPipelineFlags.useNewMobileIcons()) {
boolean show = mobile.equals("show");
String datatype = args.getString("datatype");
String slotString = args.getString("slot");
@@ -1438,7 +1447,7 @@ public class NetworkControllerImpl extends BroadcastReceiver
controller.notifyListeners();
}
String carrierNetworkChange = args.getString("carriernetworkchange");
- if (carrierNetworkChange != null) {
+ if (carrierNetworkChange != null && !mStatusBarPipelineFlags.useNewMobileIcons()) {
boolean show = carrierNetworkChange.equals("show");
for (int i = 0; i < mMobileSignalControllers.size(); i++) {
MobileSignalController controller = mMobileSignalControllers.valueAt(i);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
index 6bd9502263ff..8bb2d46c80d9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
@@ -102,6 +102,8 @@ class LockscreenSmartspaceController @Inject constructor(
private var showSensitiveContentForManagedUser = false
private var managedUserHandle: UserHandle? = null
+ // TODO(b/202758428): refactor so that we can test color updates via region samping, similar to
+ // how we test color updates when theme changes (See testThemeChangeUpdatesTextColor).
private val updateFun: UpdateColorCallback = { updateTextColorFromRegionSampler() }
// TODO: Move logic into SmartspaceView
@@ -109,16 +111,19 @@ class LockscreenSmartspaceController @Inject constructor(
override fun onViewAttachedToWindow(v: View) {
smartspaceViews.add(v as SmartspaceView)
- var regionSampler = RegionSampler(
- v,
- uiExecutor,
- bgExecutor,
- regionSamplingEnabled,
- updateFun
- )
- initializeTextColors(regionSampler)
- regionSampler.startRegionSampler()
- regionSamplers.put(v, regionSampler)
+ if (regionSamplingEnabled) {
+ var regionSampler = RegionSampler(
+ v,
+ uiExecutor,
+ bgExecutor,
+ regionSamplingEnabled,
+ updateFun
+ )
+ initializeTextColors(regionSampler)
+ regionSampler.startRegionSampler()
+ regionSamplers.put(v, regionSampler)
+ }
+
connectSession()
updateTextColorFromWallpaper()
@@ -128,9 +133,11 @@ class LockscreenSmartspaceController @Inject constructor(
override fun onViewDetachedFromWindow(v: View) {
smartspaceViews.remove(v as SmartspaceView)
- var regionSampler = regionSamplers.getValue(v)
- regionSampler.stopRegionSampler()
- regionSamplers.remove(v)
+ if (regionSamplingEnabled) {
+ var regionSampler = regionSamplers.getValue(v)
+ regionSampler.stopRegionSampler()
+ regionSamplers.remove(v)
+ }
if (smartspaceViews.isEmpty()) {
disconnect()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt
index ed7f648081c8..0eb00008e289 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt
@@ -74,8 +74,8 @@ interface Roundable {
@JvmDefault
fun requestTopRoundness(
@FloatRange(from = 0.0, to = 1.0) value: Float,
- animate: Boolean,
sourceType: SourceType,
+ animate: Boolean,
): Boolean {
val roundnessMap = roundableState.topRoundnessMap
val lastValue = roundnessMap.values.maxOrNull() ?: 0f
@@ -105,6 +105,30 @@ interface Roundable {
}
/**
+ * Request the top roundness [value] for a specific [sourceType]. Animate the roundness if the
+ * view is shown.
+ *
+ * The top roundness of a [Roundable] can be defined by different [sourceType]. In case more
+ * origins require different roundness, for the same property, the maximum value will always be
+ * chosen.
+ *
+ * @param value a value between 0f and 1f.
+ * @param sourceType the source from which the request for roundness comes.
+ * @return Whether the roundness was changed.
+ */
+ @JvmDefault
+ fun requestTopRoundness(
+ @FloatRange(from = 0.0, to = 1.0) value: Float,
+ sourceType: SourceType,
+ ): Boolean {
+ return requestTopRoundness(
+ value = value,
+ sourceType = sourceType,
+ animate = roundableState.targetView.isShown
+ )
+ }
+
+ /**
* Request the bottom roundness [value] for a specific [sourceType].
*
* The bottom roundness of a [Roundable] can be defined by different [sourceType]. In case more
@@ -119,8 +143,8 @@ interface Roundable {
@JvmDefault
fun requestBottomRoundness(
@FloatRange(from = 0.0, to = 1.0) value: Float,
- animate: Boolean,
sourceType: SourceType,
+ animate: Boolean,
): Boolean {
val roundnessMap = roundableState.bottomRoundnessMap
val lastValue = roundnessMap.values.maxOrNull() ?: 0f
@@ -149,9 +173,101 @@ interface Roundable {
return false
}
+ /**
+ * Request the bottom roundness [value] for a specific [sourceType]. Animate the roundness if
+ * the view is shown.
+ *
+ * The bottom roundness of a [Roundable] can be defined by different [sourceType]. In case more
+ * origins require different roundness, for the same property, the maximum value will always be
+ * chosen.
+ *
+ * @param value value between 0f and 1f.
+ * @param sourceType the source from which the request for roundness comes.
+ * @return Whether the roundness was changed.
+ */
+ @JvmDefault
+ fun requestBottomRoundness(
+ @FloatRange(from = 0.0, to = 1.0) value: Float,
+ sourceType: SourceType,
+ ): Boolean {
+ return requestBottomRoundness(
+ value = value,
+ sourceType = sourceType,
+ animate = roundableState.targetView.isShown
+ )
+ }
+
+ /**
+ * Request the roundness [value] for a specific [sourceType].
+ *
+ * The top/bottom roundness of a [Roundable] can be defined by different [sourceType]. In case
+ * more origins require different roundness, for the same property, the maximum value will
+ * always be chosen.
+ *
+ * @param top top value between 0f and 1f.
+ * @param bottom bottom value between 0f and 1f.
+ * @param sourceType the source from which the request for roundness comes.
+ * @param animate true if it should animate to that value.
+ * @return Whether the roundness was changed.
+ */
+ @JvmDefault
+ fun requestRoundness(
+ @FloatRange(from = 0.0, to = 1.0) top: Float,
+ @FloatRange(from = 0.0, to = 1.0) bottom: Float,
+ sourceType: SourceType,
+ animate: Boolean,
+ ): Boolean {
+ val hasTopChanged =
+ requestTopRoundness(value = top, sourceType = sourceType, animate = animate)
+ val hasBottomChanged =
+ requestBottomRoundness(value = bottom, sourceType = sourceType, animate = animate)
+ return hasTopChanged || hasBottomChanged
+ }
+
+ /**
+ * Request the roundness [value] for a specific [sourceType]. Animate the roundness if the view
+ * is shown.
+ *
+ * The top/bottom roundness of a [Roundable] can be defined by different [sourceType]. In case
+ * more origins require different roundness, for the same property, the maximum value will
+ * always be chosen.
+ *
+ * @param top top value between 0f and 1f.
+ * @param bottom bottom value between 0f and 1f.
+ * @param sourceType the source from which the request for roundness comes.
+ * @return Whether the roundness was changed.
+ */
+ @JvmDefault
+ fun requestRoundness(
+ @FloatRange(from = 0.0, to = 1.0) top: Float,
+ @FloatRange(from = 0.0, to = 1.0) bottom: Float,
+ sourceType: SourceType,
+ ): Boolean {
+ return requestRoundness(
+ top = top,
+ bottom = bottom,
+ sourceType = sourceType,
+ animate = roundableState.targetView.isShown,
+ )
+ }
+
+ /**
+ * Request the roundness 0f for a [SourceType]. Animate the roundness if the view is shown.
+ *
+ * The top/bottom roundness of a [Roundable] can be defined by different [sourceType]. In case
+ * more origins require different roundness, for the same property, the maximum value will
+ * always be chosen.
+ *
+ * @param sourceType the source from which the request for roundness comes.
+ */
+ @JvmDefault
+ fun requestRoundnessReset(sourceType: SourceType) {
+ requestRoundness(top = 0f, bottom = 0f, sourceType = sourceType)
+ }
+
/** Apply the roundness changes, usually means invalidate the [RoundableState.targetView]. */
@JvmDefault
- fun applyRoundness() {
+ fun applyRoundnessAndInvalidate() {
roundableState.targetView.invalidate()
}
@@ -227,7 +343,7 @@ class RoundableState(
/** Set the current top roundness */
internal fun setTopRoundness(
value: Float,
- animated: Boolean = targetView.isShown,
+ animated: Boolean,
) {
PropertyAnimator.setProperty(targetView, topAnimatable, value, DURATION, animated)
}
@@ -235,11 +351,19 @@ class RoundableState(
/** Set the current bottom roundness */
internal fun setBottomRoundness(
value: Float,
- animated: Boolean = targetView.isShown,
+ animated: Boolean,
) {
PropertyAnimator.setProperty(targetView, bottomAnimatable, value, DURATION, animated)
}
+ fun debugString() = buildString {
+ append("TargetView: ${targetView.hashCode()} ")
+ append("Top: $topRoundness ")
+ append(topRoundnessMap.map { "${it.key} ${it.value}" })
+ append(" Bottom: $bottomRoundness ")
+ append(bottomRoundnessMap.map { "${it.key} ${it.value}" })
+ }
+
companion object {
private val DURATION: AnimationProperties =
AnimationProperties()
@@ -252,7 +376,7 @@ class RoundableState(
override fun setValue(view: View, value: Float) {
roundable.roundableState.topRoundness = value
- roundable.applyRoundness()
+ roundable.applyRoundnessAndInvalidate()
}
},
R.id.top_roundess_animator_tag,
@@ -267,7 +391,7 @@ class RoundableState(
override fun setValue(view: View, value: Float) {
roundable.roundableState.bottomRoundness = value
- roundable.applyRoundness()
+ roundable.applyRoundnessAndInvalidate()
}
},
R.id.bottom_roundess_animator_tag,
@@ -277,7 +401,31 @@ class RoundableState(
}
}
-enum class SourceType {
+/**
+ * Interface used to define the owner of a roundness. Usually the [SourceType] is defined as a
+ * private property of a class.
+ */
+interface SourceType {
+ companion object {
+ /**
+ * This is the most convenient way to define a new [SourceType].
+ *
+ * For example:
+ *
+ * ```kotlin
+ * private val SECTION = SourceType.from("Section")
+ * ```
+ */
+ @JvmStatic
+ fun from(name: String) =
+ object : SourceType {
+ override fun toString() = name
+ }
+ }
+}
+
+@Deprecated("Use SourceType.from() instead", ReplaceWith("SourceType.from()"))
+enum class LegacySourceType : SourceType {
DefaultValue,
OnDismissAnimation,
OnScroll,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
index 6e5fcebf780f..9da94ce968c7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
@@ -28,6 +28,7 @@ import com.android.systemui.statusbar.notification.collection.coordinator.dagger
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
import com.android.systemui.statusbar.notification.collection.provider.SectionHeaderVisibilityProvider
+import com.android.systemui.statusbar.notification.collection.provider.SeenNotificationsProviderImpl
import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider
import javax.inject.Inject
import kotlin.time.Duration.Companion.seconds
@@ -49,6 +50,7 @@ constructor(
private val notifPipelineFlags: NotifPipelineFlags,
@Application private val scope: CoroutineScope,
private val sectionHeaderVisibilityProvider: SectionHeaderVisibilityProvider,
+ private val seenNotifsProvider: SeenNotificationsProviderImpl,
private val statusBarStateController: StatusBarStateController,
) : Coordinator {
@@ -105,6 +107,9 @@ constructor(
@VisibleForTesting
internal val unseenNotifFilter =
object : NotifFilter("$TAG-unseen") {
+
+ var hasFilteredAnyNotifs = false
+
override fun shouldFilterOut(entry: NotificationEntry, now: Long): Boolean =
when {
// Don't apply filter if the keyguard isn't currently showing
@@ -115,7 +120,12 @@ constructor(
// - summary will be pruned if necessary, depending on if children are filtered
entry.parent?.summary == entry -> false
else -> true
- }
+ }.also { hasFiltered -> hasFilteredAnyNotifs = hasFilteredAnyNotifs || hasFiltered }
+
+ override fun onCleanup() {
+ seenNotifsProvider.hasFilteredOutSeenNotifications = hasFilteredAnyNotifs
+ hasFilteredAnyNotifs = false
+ }
}
private val notifFilter: NotifFilter =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/Pluggable.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/Pluggable.java
index 966ab4c61b50..afdadeb74e23 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/Pluggable.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/Pluggable.java
@@ -51,7 +51,9 @@ public abstract class Pluggable<This> {
*/
public final void invalidateList(@Nullable String reason) {
if (mListener != null) {
- Trace.beginSection("Pluggable<" + mName + ">.invalidateList");
+ if (Trace.isEnabled()) {
+ Trace.traceBegin(Trace.TRACE_TAG_APP, "Pluggable<" + mName + ">.invalidateList");
+ }
mListener.onPluggableInvalidated((This) this, reason);
Trace.endSection();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/SeenNotificationsProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/SeenNotificationsProvider.kt
new file mode 100644
index 000000000000..cff47e220299
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/SeenNotificationsProvider.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.provider
+
+import com.android.systemui.dagger.SysUISingleton
+import dagger.Binds
+import dagger.Module
+import javax.inject.Inject
+
+/** Keeps track of whether "seen" notification content has been filtered out of the shade. */
+interface SeenNotificationsProvider {
+ /** Are any already-seen notifications currently filtered out of the shade? */
+ val hasFilteredOutSeenNotifications: Boolean
+}
+
+@Module
+interface SeenNotificationsProviderModule {
+ @Binds
+ fun bindSeenNotificationsProvider(
+ impl: SeenNotificationsProviderImpl
+ ): SeenNotificationsProvider
+}
+
+@SysUISingleton
+class SeenNotificationsProviderImpl @Inject constructor() : SeenNotificationsProvider {
+ override var hasFilteredOutSeenNotifications: Boolean = false
+}
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 a7b7a239f4c2..808638a99dfa 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
@@ -52,6 +52,7 @@ import com.android.systemui.statusbar.notification.collection.inflation.OnUserIn
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
import com.android.systemui.statusbar.notification.collection.provider.NotificationVisibilityProviderImpl;
+import com.android.systemui.statusbar.notification.collection.provider.SeenNotificationsProviderModule;
import com.android.systemui.statusbar.notification.collection.provider.VisibilityLocationProviderDelegator;
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManagerImpl;
@@ -96,6 +97,7 @@ import dagger.Provides;
@Module(includes = {
CoordinatorsModule.class,
KeyguardNotificationVisibilityProviderModule.class,
+ SeenNotificationsProviderModule.class,
ShadeEventsModule.class,
NotifPipelineChoreographerModule.class,
NotificationSectionHeadersModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
index d29298a2f637..fbe88dff07f1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
@@ -39,9 +39,13 @@ import com.android.systemui.animation.Interpolators;
import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.notification.FakeShadowView;
import com.android.systemui.statusbar.notification.NotificationUtils;
+import com.android.systemui.statusbar.notification.SourceType;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
+import java.util.HashSet;
+import java.util.Set;
+
/**
* Base class for both {@link ExpandableNotificationRow} and {@link NotificationShelf}
* to implement dimming/activating on Keyguard for the double-tap gesture
@@ -91,6 +95,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
= new PathInterpolator(0.6f, 0, 0.5f, 1);
private static final Interpolator ACTIVATE_INVERSE_ALPHA_INTERPOLATOR
= new PathInterpolator(0, 0, 0.5f, 1);
+ private final Set<SourceType> mOnDetachResetRoundness = new HashSet<>();
private int mTintedRippleColor;
private int mNormalRippleColor;
private Gefingerpoken mTouchHandler;
@@ -134,6 +139,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
private boolean mDismissed;
private boolean mRefocusOnDismiss;
private AccessibilityManager mAccessibilityManager;
+ protected boolean mUseRoundnessSourceTypes;
public ActivatableNotificationView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -613,9 +619,9 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
protected void resetAllContentAlphas() {}
@Override
- public void applyRoundness() {
- super.applyRoundness();
+ public void applyRoundnessAndInvalidate() {
applyBackgroundRoundness(getTopCornerRadius(), getBottomCornerRadius());
+ super.applyRoundnessAndInvalidate();
}
@Override
@@ -775,6 +781,33 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
mAccessibilityManager = accessibilityManager;
}
+ /**
+ * Enable the support for rounded corner based on the SourceType
+ * @param enabled true if is supported
+ */
+ public void useRoundnessSourceTypes(boolean enabled) {
+ mUseRoundnessSourceTypes = enabled;
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ if (mUseRoundnessSourceTypes && !mOnDetachResetRoundness.isEmpty()) {
+ for (SourceType sourceType : mOnDetachResetRoundness) {
+ requestRoundnessReset(sourceType);
+ }
+ mOnDetachResetRoundness.clear();
+ }
+ }
+
+ /**
+ * SourceType which should be reset when this View is detached
+ * @param sourceType will be reset on View detached
+ */
+ public void addOnDetachResetRoundness(SourceType sourceType) {
+ mOnDetachResetRoundness.add(sourceType);
+ }
+
public interface OnActivatedListener {
void onActivated(ActivatableNotificationView view);
void onActivationReset(ActivatableNotificationView view);
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 d7d5ac961249..c7c1634ea105 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
@@ -91,6 +91,7 @@ import com.android.systemui.statusbar.StatusBarIconView;
import com.android.systemui.statusbar.notification.AboveShelfChangedListener;
import com.android.systemui.statusbar.notification.FeedbackIcon;
import com.android.systemui.statusbar.notification.LaunchAnimationParameters;
+import com.android.systemui.statusbar.notification.LegacySourceType;
import com.android.systemui.statusbar.notification.NotificationFadeAware;
import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorController;
import com.android.systemui.statusbar.notification.NotificationUtils;
@@ -143,6 +144,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
private static final int MENU_VIEW_INDEX = 0;
public static final float DEFAULT_HEADER_VISIBLE_AMOUNT = 1.0f;
private static final long RECENTLY_ALERTED_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(30);
+ private static final SourceType BASE_VALUE = SourceType.from("BaseValue");
+ private static final SourceType FROM_PARENT = SourceType.from("FromParent(ENR)");
+ private static final SourceType PINNED = SourceType.from("Pinned");
// We don't correctly track dark mode until the content views are inflated, so always update
// the background on first content update just in case it happens to be during a theme change.
@@ -150,6 +154,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
private boolean mNotificationTranslationFinished = false;
private boolean mIsSnoozed;
private boolean mIsFaded;
+ private boolean mAnimatePinnedRoundness = false;
/**
* Listener for when {@link ExpandableNotificationRow} is laid out.
@@ -376,7 +381,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
private float mTopRoundnessDuringLaunchAnimation;
private float mBottomRoundnessDuringLaunchAnimation;
- private boolean mIsNotificationGroupCornerEnabled;
+ private float mSmallRoundness;
/**
* Returns whether the given {@code statusBarNotification} is a system notification.
@@ -844,7 +849,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
onAttachedChildrenCountChanged();
row.setIsChildInGroup(false, null);
- row.requestBottomRoundness(0.0f, /* animate = */ false, SourceType.DefaultValue);
+ if (!mUseRoundnessSourceTypes) {
+ row.requestBottomRoundness(0.0f, LegacySourceType.DefaultValue, /* animate = */ false);
+ }
}
/**
@@ -860,7 +867,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
if (child.keepInParentForDismissAnimation()) {
mChildrenContainer.removeNotification(child);
child.setIsChildInGroup(false, null);
- child.requestBottomRoundness(0.0f, /* animate = */ false, SourceType.DefaultValue);
+ if (!mUseRoundnessSourceTypes) {
+ LegacySourceType sourceType = LegacySourceType.DefaultValue;
+ child.requestBottomRoundness(0f, sourceType, /* animate = */ false);
+ }
child.setKeepInParentForDismissAnimation(false);
logKeepInParentChildDetached(child);
childCountChanged = true;
@@ -915,6 +925,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
mNotificationParent.updateBackgroundForGroupState();
}
updateBackgroundClipping();
+ if (mUseRoundnessSourceTypes) {
+ updateBaseRoundness();
+ }
}
@Override
@@ -1033,6 +1046,16 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
if (isAboveShelf() != wasAboveShelf) {
mAboveShelfChangedListener.onAboveShelfStateChanged(!wasAboveShelf);
}
+ if (mUseRoundnessSourceTypes) {
+ if (pinned) {
+ // Should be animated if someone explicitly set it to 0 and the row is shown.
+ boolean animated = mAnimatePinnedRoundness && isShown();
+ requestRoundness(/* top = */ 1f, /* bottom = */ 1f, PINNED, animated);
+ } else {
+ requestRoundnessReset(PINNED);
+ mAnimatePinnedRoundness = true;
+ }
+ }
}
@Override
@@ -1607,6 +1630,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
super(context, attrs);
mImageResolver = new NotificationInlineImageResolver(context,
new NotificationInlineImageCache());
+ float radius = getResources().getDimension(R.dimen.notification_corner_radius_small);
+ mSmallRoundness = radius / getMaxRadius();
initDimens();
}
@@ -1839,7 +1864,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
mChildrenContainer.setIsLowPriority(mIsLowPriority);
mChildrenContainer.setContainingNotification(ExpandableNotificationRow.this);
mChildrenContainer.onNotificationUpdated();
- mChildrenContainer.enableNotificationGroupCorner(mIsNotificationGroupCornerEnabled);
+ mChildrenContainer.useRoundnessSourceTypes(mUseRoundnessSourceTypes);
mTranslateableViews.add(mChildrenContainer);
});
@@ -2271,7 +2296,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
@Override
public float getTopRoundness() {
- if (mExpandAnimationRunning) {
+ if (!mUseRoundnessSourceTypes && mExpandAnimationRunning) {
return mTopRoundnessDuringLaunchAnimation;
}
@@ -2280,7 +2305,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
@Override
public float getBottomRoundness() {
- if (mExpandAnimationRunning) {
+ if (!mUseRoundnessSourceTypes && mExpandAnimationRunning) {
return mBottomRoundnessDuringLaunchAnimation;
}
@@ -3436,17 +3461,24 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
@Override
- public void applyRoundness() {
- super.applyRoundness();
+ public void applyRoundnessAndInvalidate() {
applyChildrenRoundness();
+ super.applyRoundnessAndInvalidate();
}
private void applyChildrenRoundness() {
if (mIsSummaryWithChildren) {
- mChildrenContainer.requestBottomRoundness(
- getBottomRoundness(),
- /* animate = */ false,
- SourceType.DefaultValue);
+ if (mUseRoundnessSourceTypes) {
+ mChildrenContainer.requestRoundness(
+ /* top = */ getTopRoundness(),
+ /* bottom = */ getBottomRoundness(),
+ FROM_PARENT);
+ } else {
+ mChildrenContainer.requestBottomRoundness(
+ getBottomRoundness(),
+ LegacySourceType.DefaultValue,
+ /* animate = */ false);
+ }
}
}
@@ -3605,6 +3637,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
} else {
pw.println("no viewState!!!");
}
+ pw.println("Roundness: " + getRoundableState().debugString());
if (mIsSummaryWithChildren) {
pw.println();
@@ -3649,14 +3682,38 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
return mTargetPoint;
}
+ /** Update the minimum roundness based on current state */
+ private void updateBaseRoundness() {
+ if (isChildInGroup()) {
+ requestRoundnessReset(BASE_VALUE);
+ } else {
+ requestRoundness(mSmallRoundness, mSmallRoundness, BASE_VALUE);
+ }
+ }
+
/**
- * Enable the support for rounded corner in notification group
+ * Enable the support for rounded corner based on the SourceType
* @param enabled true if is supported
*/
- public void enableNotificationGroupCorner(boolean enabled) {
- mIsNotificationGroupCornerEnabled = enabled;
+ @Override
+ public void useRoundnessSourceTypes(boolean enabled) {
+ super.useRoundnessSourceTypes(enabled);
if (mChildrenContainer != null) {
- mChildrenContainer.enableNotificationGroupCorner(mIsNotificationGroupCornerEnabled);
+ mChildrenContainer.useRoundnessSourceTypes(mUseRoundnessSourceTypes);
+ }
+ }
+
+ @Override
+ public String toString() {
+ String roundableStateDebug = "RoundableState = " + getRoundableState().debugString();
+ return "ExpandableNotificationRow:" + hashCode() + " { " + roundableStateDebug + " }";
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ if (mUseRoundnessSourceTypes) {
+ updateBaseRoundness();
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
index 8a400d5fef99..d1138608805b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
@@ -255,8 +255,8 @@ public class ExpandableNotificationRowController implements NotifViewController
mStatusBarStateController.removeCallback(mStatusBarStateListener);
}
});
- mView.enableNotificationGroupCorner(
- mFeatureFlags.isEnabled(Flags.NOTIFICATION_GROUP_CORNER));
+ mView.useRoundnessSourceTypes(
+ mFeatureFlags.isEnabled(Flags.USE_ROUNDNESS_SOURCETYPES));
}
private final StatusBarStateController.StateListener mStatusBarStateListener =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
index 232462714f7d..0213b969551e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
@@ -219,9 +219,9 @@ public abstract class ExpandableOutlineView extends ExpandableView {
}
@Override
- public void applyRoundness() {
+ public void applyRoundnessAndInvalidate() {
invalidateOutline();
- super.applyRoundness();
+ super.applyRoundnessAndInvalidate();
}
protected void setBackgroundTop(int backgroundTop) {
@@ -233,7 +233,7 @@ public abstract class ExpandableOutlineView extends ExpandableView {
public void onDensityOrFontScaleChanged() {
initDimens();
- applyRoundness();
+ applyRoundnessAndInvalidate();
}
@Override
@@ -241,7 +241,7 @@ public abstract class ExpandableOutlineView extends ExpandableView {
int previousHeight = getActualHeight();
super.setActualHeight(actualHeight, notifyListeners);
if (previousHeight != actualHeight) {
- applyRoundness();
+ applyRoundnessAndInvalidate();
}
}
@@ -250,7 +250,7 @@ public abstract class ExpandableOutlineView extends ExpandableView {
int previousAmount = getClipTopAmount();
super.setClipTopAmount(clipTopAmount);
if (previousAmount != clipTopAmount) {
- applyRoundness();
+ applyRoundnessAndInvalidate();
}
}
@@ -259,14 +259,14 @@ public abstract class ExpandableOutlineView extends ExpandableView {
int previousAmount = getClipBottomAmount();
super.setClipBottomAmount(clipBottomAmount);
if (previousAmount != clipBottomAmount) {
- applyRoundness();
+ applyRoundnessAndInvalidate();
}
}
protected void setOutlineAlpha(float alpha) {
if (alpha != mOutlineAlpha) {
mOutlineAlpha = alpha;
- applyRoundness();
+ applyRoundnessAndInvalidate();
}
}
@@ -280,7 +280,7 @@ public abstract class ExpandableOutlineView extends ExpandableView {
setOutlineRect(rect.left, rect.top, rect.right, rect.bottom);
} else {
mCustomOutline = false;
- applyRoundness();
+ applyRoundnessAndInvalidate();
}
}
@@ -340,7 +340,7 @@ public abstract class ExpandableOutlineView extends ExpandableView {
// Outlines need to be at least 1 dp
mOutlineRect.bottom = (int) Math.max(top, mOutlineRect.bottom);
mOutlineRect.right = (int) Math.max(left, mOutlineRect.right);
- applyRoundness();
+ applyRoundnessAndInvalidate();
}
public Path getCustomClipPath(View child) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
index f13e48d55ae4..1f664cb16179 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
@@ -71,6 +71,8 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper imple
private View mFeedbackIcon;
private boolean mIsLowPriority;
private boolean mTransformLowPriorityTitle;
+ private boolean mUseRoundnessSourceTypes;
+ private RoundnessChangedListener mRoundnessChangedListener;
protected NotificationHeaderViewWrapper(Context ctx, View view, ExpandableNotificationRow row) {
super(ctx, view, row);
@@ -117,6 +119,20 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper imple
return mRoundableState;
}
+ @Override
+ public void applyRoundnessAndInvalidate() {
+ if (mUseRoundnessSourceTypes && mRoundnessChangedListener != null) {
+ // We cannot apply the rounded corner to this View, so our parents (in drawChild()) will
+ // clip our canvas. So we should invalidate our parent.
+ mRoundnessChangedListener.applyRoundnessAndInvalidate();
+ }
+ Roundable.super.applyRoundnessAndInvalidate();
+ }
+
+ public void setOnRoundnessChangedListener(RoundnessChangedListener listener) {
+ mRoundnessChangedListener = listener;
+ }
+
protected void resolveHeaderViews() {
mIcon = mView.findViewById(com.android.internal.R.id.icon);
mHeaderText = mView.findViewById(com.android.internal.R.id.header_text);
@@ -343,4 +359,23 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper imple
}
}
}
+
+ /**
+ * Enable the support for rounded corner based on the SourceType
+ *
+ * @param enabled true if is supported
+ */
+ public void useRoundnessSourceTypes(boolean enabled) {
+ mUseRoundnessSourceTypes = enabled;
+ }
+
+ /**
+ * Interface that handle the Roundness changes
+ */
+ public interface RoundnessChangedListener {
+ /**
+ * This method will be called when this class call applyRoundnessAndInvalidate()
+ */
+ void applyRoundnessAndInvalidate();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
index d43ca823089f..4a8e2dbb5334 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
@@ -47,6 +47,7 @@ import com.android.systemui.statusbar.CrossFadeHelper;
import com.android.systemui.statusbar.NotificationGroupingUtil;
import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.notification.FeedbackIcon;
+import com.android.systemui.statusbar.notification.LegacySourceType;
import com.android.systemui.statusbar.notification.NotificationFadeAware;
import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.Roundable;
@@ -83,6 +84,7 @@ public class NotificationChildrenContainer extends ViewGroup
return mAnimationFilter;
}
}.setDuration(200);
+ private static final SourceType FROM_PARENT = SourceType.from("FromParent(NCC)");
private final List<View> mDividers = new ArrayList<>();
private final List<ExpandableNotificationRow> mAttachedChildren = new ArrayList<>();
@@ -131,7 +133,7 @@ public class NotificationChildrenContainer extends ViewGroup
private int mUntruncatedChildCount;
private boolean mContainingNotificationIsFaded = false;
private RoundableState mRoundableState;
- private boolean mIsNotificationGroupCornerEnabled;
+ private boolean mUseRoundnessSourceTypes;
public NotificationChildrenContainer(Context context) {
this(context, null);
@@ -313,9 +315,12 @@ public class NotificationChildrenContainer extends ViewGroup
row.setContentTransformationAmount(0, false /* isLastChild */);
row.setNotificationFaded(mContainingNotificationIsFaded);
- // This is a workaround, the NotificationShelf should be the owner of `OnScroll` roundness.
- // Here we should reset the `OnScroll` roundness only on top-level rows.
- NotificationShelf.resetOnScrollRoundness(row);
+ if (!mUseRoundnessSourceTypes) {
+ // This is a workaround, the NotificationShelf should be the owner of `OnScroll`
+ // roundness.
+ // Here we should reset the `OnScroll` roundness only on top-level rows.
+ NotificationShelf.resetLegacyOnScrollRoundness(row);
+ }
// It doesn't make sense to keep old animations around, lets cancel them!
ExpandableViewState viewState = row.getViewState();
@@ -323,6 +328,10 @@ public class NotificationChildrenContainer extends ViewGroup
viewState.cancelAnimations(row);
row.cancelAppearDrawing();
}
+
+ if (mUseRoundnessSourceTypes) {
+ applyRoundnessAndInvalidate();
+ }
}
private void ensureRemovedFromTransientContainer(View v) {
@@ -356,6 +365,11 @@ public class NotificationChildrenContainer extends ViewGroup
if (!row.isRemoved()) {
mGroupingUtil.restoreChildNotification(row);
}
+
+ if (mUseRoundnessSourceTypes) {
+ row.requestRoundnessReset(FROM_PARENT);
+ applyRoundnessAndInvalidate();
+ }
}
/**
@@ -382,6 +396,10 @@ public class NotificationChildrenContainer extends ViewGroup
getContext(),
mNotificationHeader,
mContainingNotification);
+ mNotificationHeaderWrapper.useRoundnessSourceTypes(mUseRoundnessSourceTypes);
+ if (mUseRoundnessSourceTypes) {
+ mNotificationHeaderWrapper.setOnRoundnessChangedListener(this::invalidate);
+ }
addView(mNotificationHeader, 0);
invalidate();
} else {
@@ -419,6 +437,12 @@ public class NotificationChildrenContainer extends ViewGroup
getContext(),
mNotificationHeaderLowPriority,
mContainingNotification);
+ mNotificationHeaderWrapperLowPriority.useRoundnessSourceTypes(
+ mUseRoundnessSourceTypes
+ );
+ if (mUseRoundnessSourceTypes) {
+ mNotificationHeaderWrapper.setOnRoundnessChangedListener(this::invalidate);
+ }
addView(mNotificationHeaderLowPriority, 0);
invalidate();
} else {
@@ -841,7 +865,7 @@ public class NotificationChildrenContainer extends ViewGroup
isCanvasChanged = true;
canvas.save();
- if (mIsNotificationGroupCornerEnabled && translation != 0f) {
+ if (mUseRoundnessSourceTypes && translation != 0f) {
clipPath.offset(translation, 0f);
canvas.clipPath(clipPath);
clipPath.offset(-translation, 0f);
@@ -1392,24 +1416,28 @@ public class NotificationChildrenContainer extends ViewGroup
}
@Override
- public void applyRoundness() {
- Roundable.super.applyRoundness();
+ public void applyRoundnessAndInvalidate() {
boolean last = true;
for (int i = mAttachedChildren.size() - 1; i >= 0; i--) {
ExpandableNotificationRow child = mAttachedChildren.get(i);
if (child.getVisibility() == View.GONE) {
continue;
}
- child.requestTopRoundness(
- /* value = */ 0f,
- /* animate = */ isShown(),
- SourceType.DefaultValue);
- child.requestBottomRoundness(
- /* value = */ last ? getBottomRoundness() : 0f,
- /* animate = */ isShown(),
- SourceType.DefaultValue);
+ if (mUseRoundnessSourceTypes) {
+ child.requestRoundness(
+ /* top = */ 0f,
+ /* bottom = */ last ? getBottomRoundness() : 0f,
+ FROM_PARENT);
+ } else {
+ child.requestRoundness(
+ /* top = */ 0f,
+ /* bottom = */ last ? getBottomRoundness() : 0f,
+ LegacySourceType.DefaultValue,
+ /* animate = */ isShown());
+ }
last = false;
}
+ Roundable.super.applyRoundnessAndInvalidate();
}
public void setHeaderVisibleAmount(float headerVisibleAmount) {
@@ -1467,10 +1495,17 @@ public class NotificationChildrenContainer extends ViewGroup
}
/**
- * Enable the support for rounded corner in notification group
+ * Enable the support for rounded corner based on the SourceType
+ *
* @param enabled true if is supported
*/
- public void enableNotificationGroupCorner(boolean enabled) {
- mIsNotificationGroupCornerEnabled = enabled;
+ public void useRoundnessSourceTypes(boolean enabled) {
+ mUseRoundnessSourceTypes = enabled;
+ }
+
+ @Override
+ public String toString() {
+ String roundableStateDebug = "RoundableState = " + getRoundableState().debugString();
+ return "NotificationChildrenContainer:" + hashCode() + " { " + roundableStateDebug + " }";
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
index 6810055ad3bf..fde8c4d453ce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
@@ -25,6 +25,9 @@ import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
+import com.android.systemui.statusbar.notification.LegacySourceType;
import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
import com.android.systemui.statusbar.notification.Roundable;
import com.android.systemui.statusbar.notification.SourceType;
@@ -45,6 +48,7 @@ import javax.inject.Inject;
public class NotificationRoundnessManager implements Dumpable {
private static final String TAG = "NotificationRoundnessManager";
+ private static final SourceType DISMISS_ANIMATION = SourceType.from("DismissAnimation");
private final ExpandableView[] mFirstInSectionViews;
private final ExpandableView[] mLastInSectionViews;
@@ -63,12 +67,14 @@ public class NotificationRoundnessManager implements Dumpable {
private ExpandableView mSwipedView = null;
private Roundable mViewBeforeSwipedView = null;
private Roundable mViewAfterSwipedView = null;
+ private boolean mUseRoundnessSourceTypes;
@Inject
NotificationRoundnessManager(
NotificationSectionsFeatureManager sectionsFeatureManager,
NotificationRoundnessLogger notifLogger,
- DumpManager dumpManager) {
+ DumpManager dumpManager,
+ FeatureFlags featureFlags) {
int numberOfSections = sectionsFeatureManager.getNumberOfBuckets();
mFirstInSectionViews = new ExpandableView[numberOfSections];
mLastInSectionViews = new ExpandableView[numberOfSections];
@@ -76,6 +82,7 @@ public class NotificationRoundnessManager implements Dumpable {
mTmpLastInSectionViews = new ExpandableView[numberOfSections];
mNotifLogger = notifLogger;
mDumpManager = dumpManager;
+ mUseRoundnessSourceTypes = featureFlags.isEnabled(Flags.USE_ROUNDNESS_SOURCETYPES);
mDumpManager.registerDumpable(TAG, this);
}
@@ -94,6 +101,7 @@ public class NotificationRoundnessManager implements Dumpable {
}
public void updateView(ExpandableView view, boolean animate) {
+ if (mUseRoundnessSourceTypes) return;
boolean changed = updateViewWithoutCallback(view, animate);
if (changed) {
mRoundingChangedCallback.run();
@@ -110,6 +118,7 @@ public class NotificationRoundnessManager implements Dumpable {
boolean updateViewWithoutCallback(
ExpandableView view,
boolean animate) {
+ if (mUseRoundnessSourceTypes) return false;
if (view == null
|| view == mViewBeforeSwipedView
|| view == mViewAfterSwipedView) {
@@ -118,13 +127,13 @@ public class NotificationRoundnessManager implements Dumpable {
final boolean isTopChanged = view.requestTopRoundness(
getRoundnessDefaultValue(view, true /* top */),
- animate,
- SourceType.DefaultValue);
+ LegacySourceType.DefaultValue,
+ animate);
final boolean isBottomChanged = view.requestBottomRoundness(
getRoundnessDefaultValue(view, /* top = */ false),
- animate,
- SourceType.DefaultValue);
+ LegacySourceType.DefaultValue,
+ animate);
final boolean isFirstInSection = isFirstInSection(view);
final boolean isLastInSection = isLastInSection(view);
@@ -139,6 +148,7 @@ public class NotificationRoundnessManager implements Dumpable {
}
private boolean isFirstInSection(ExpandableView view) {
+ if (mUseRoundnessSourceTypes) return false;
for (int i = 0; i < mFirstInSectionViews.length; i++) {
if (view == mFirstInSectionViews[i]) {
return true;
@@ -148,6 +158,7 @@ public class NotificationRoundnessManager implements Dumpable {
}
private boolean isLastInSection(ExpandableView view) {
+ if (mUseRoundnessSourceTypes) return false;
for (int i = mLastInSectionViews.length - 1; i >= 0; i--) {
if (view == mLastInSectionViews[i]) {
return true;
@@ -160,9 +171,6 @@ public class NotificationRoundnessManager implements Dumpable {
Roundable viewBefore,
ExpandableView viewSwiped,
Roundable viewAfter) {
- final boolean animate = true;
- final SourceType source = SourceType.OnDismissAnimation;
-
// This method requires you to change the roundness of the current View targets and reset
// the roundness of the old View targets (if any) to 0f.
// To avoid conflicts, it generates a set of old Views and removes the current Views
@@ -172,31 +180,34 @@ public class NotificationRoundnessManager implements Dumpable {
if (mSwipedView != null) oldViews.add(mSwipedView);
if (mViewAfterSwipedView != null) oldViews.add(mViewAfterSwipedView);
+ final SourceType source;
+ if (mUseRoundnessSourceTypes) {
+ source = DISMISS_ANIMATION;
+ } else {
+ source = LegacySourceType.OnDismissAnimation;
+ }
+
mViewBeforeSwipedView = viewBefore;
if (viewBefore != null) {
oldViews.remove(viewBefore);
- viewBefore.requestTopRoundness(0f, animate, source);
- viewBefore.requestBottomRoundness(1f, animate, source);
+ viewBefore.requestRoundness(/* top = */ 0f, /* bottom = */ 1f, source);
}
mSwipedView = viewSwiped;
if (viewSwiped != null) {
oldViews.remove(viewSwiped);
- viewSwiped.requestTopRoundness(1f, animate, source);
- viewSwiped.requestBottomRoundness(1f, animate, source);
+ viewSwiped.requestRoundness(/* top = */ 1f, /* bottom = */ 1f, source);
}
mViewAfterSwipedView = viewAfter;
if (viewAfter != null) {
oldViews.remove(viewAfter);
- viewAfter.requestTopRoundness(1f, animate, source);
- viewAfter.requestBottomRoundness(0f, animate, source);
+ viewAfter.requestRoundness(/* top = */ 1f, /* bottom = */ 0f, source);
}
// After setting the current Views, reset the views that are still present in the set.
for (Roundable oldView : oldViews) {
- oldView.requestTopRoundness(0f, animate, source);
- oldView.requestBottomRoundness(0f, animate, source);
+ oldView.requestRoundnessReset(source);
}
}
@@ -204,7 +215,23 @@ public class NotificationRoundnessManager implements Dumpable {
mIsClearAllInProgress = isClearingAll;
}
+ /**
+ * Check if "Clear all" notifications is in progress.
+ */
+ public boolean isClearAllInProgress() {
+ return mIsClearAllInProgress;
+ }
+
+ /**
+ * Check if we can request the `Pulsing` roundness for notification.
+ */
+ public boolean shouldRoundNotificationPulsing() {
+ return mRoundForPulsingViews;
+ }
+
private float getRoundnessDefaultValue(Roundable view, boolean top) {
+ if (mUseRoundnessSourceTypes) return 0f;
+
if (view == null) {
return 0f;
}
@@ -250,6 +277,7 @@ public class NotificationRoundnessManager implements Dumpable {
}
public void setExpanded(float expandedHeight, float appearFraction) {
+ if (mUseRoundnessSourceTypes) return;
mExpanded = expandedHeight != 0.0f;
mAppearFraction = appearFraction;
if (mTrackedHeadsUp != null) {
@@ -258,6 +286,7 @@ public class NotificationRoundnessManager implements Dumpable {
}
public void updateRoundedChildren(NotificationSection[] sections) {
+ if (mUseRoundnessSourceTypes) return;
boolean anyChanged = false;
for (int i = 0; i < sections.length; i++) {
mTmpFirstInSectionViews[i] = mFirstInSectionViews[i];
@@ -280,6 +309,7 @@ public class NotificationRoundnessManager implements Dumpable {
NotificationSection[] sections,
ExpandableView[] oldViews,
boolean first) {
+ if (mUseRoundnessSourceTypes) return false;
boolean anyChanged = false;
for (ExpandableView oldView : oldViews) {
if (oldView != null) {
@@ -313,6 +343,7 @@ public class NotificationRoundnessManager implements Dumpable {
NotificationSection[] sections,
ExpandableView[] oldViews,
boolean first) {
+ if (mUseRoundnessSourceTypes) return false;
boolean anyChanged = false;
for (NotificationSection section : sections) {
ExpandableView newView =
@@ -339,6 +370,15 @@ public class NotificationRoundnessManager implements Dumpable {
mAnimatedChildren = animatedChildren;
}
+ /**
+ * Check if the view should be animated
+ * @param view target view
+ * @return true, if is in the AnimatedChildren set
+ */
+ public boolean isAnimatedChild(ExpandableView view) {
+ return mAnimatedChildren.contains(view);
+ }
+
public void setOnRoundingChangedCallback(Runnable roundingChangedCallback) {
mRoundingChangedCallback = roundingChangedCallback;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
index a1b77acb9a5e..070b4394291f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
@@ -19,8 +19,11 @@ import android.annotation.ColorInt
import android.util.Log
import android.view.View
import com.android.internal.annotations.VisibleForTesting
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.media.controls.ui.KeyguardMediaController
import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager
+import com.android.systemui.statusbar.notification.SourceType
import com.android.systemui.statusbar.notification.collection.render.MediaContainerController
import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController
import com.android.systemui.statusbar.notification.dagger.AlertingHeader
@@ -44,12 +47,16 @@ class NotificationSectionsManager @Inject internal constructor(
private val keyguardMediaController: KeyguardMediaController,
private val sectionsFeatureManager: NotificationSectionsFeatureManager,
private val mediaContainerController: MediaContainerController,
+ private val notificationRoundnessManager: NotificationRoundnessManager,
@IncomingHeader private val incomingHeaderController: SectionHeaderController,
@PeopleHeader private val peopleHeaderController: SectionHeaderController,
@AlertingHeader private val alertingHeaderController: SectionHeaderController,
- @SilentHeader private val silentHeaderController: SectionHeaderController
+ @SilentHeader private val silentHeaderController: SectionHeaderController,
+ featureFlags: FeatureFlags
) : SectionProvider {
+ private val useRoundnessSourceTypes = featureFlags.isEnabled(Flags.USE_ROUNDNESS_SOURCETYPES)
+
private val configurationListener = object : ConfigurationController.ConfigurationListener {
override fun onLocaleListChanged() {
reinflateViews()
@@ -177,11 +184,49 @@ class NotificationSectionsManager @Inject internal constructor(
size = sections.size,
operation = SectionBounds::addNotif
)
+
+ // Build a set of the old first/last Views of the sections
+ val oldFirstChildren = sections.mapNotNull { it.firstVisibleChild }.toSet().toMutableSet()
+ val oldLastChildren = sections.mapNotNull { it.lastVisibleChild }.toSet().toMutableSet()
+
// Update each section with the associated boundary, tracking if there was a change
val changed = sections.fold(false) { changed, section ->
val bounds = sectionBounds[section.bucket] ?: SectionBounds.None
- bounds.updateSection(section) || changed
+ val isSectionChanged = bounds.updateSection(section)
+ isSectionChanged || changed
+ }
+
+ if (useRoundnessSourceTypes) {
+ val newFirstChildren = sections.mapNotNull { it.firstVisibleChild }
+ val newLastChildren = sections.mapNotNull { it.lastVisibleChild }
+
+ // Update the roundness of Views that weren't already in the first/last position
+ newFirstChildren.forEach { firstChild ->
+ val wasFirstChild = oldFirstChildren.remove(firstChild)
+ if (!wasFirstChild) {
+ val notAnimatedChild = !notificationRoundnessManager.isAnimatedChild(firstChild)
+ val animated = firstChild.isShown && notAnimatedChild
+ firstChild.requestTopRoundness(1f, SECTION, animated)
+ }
+ }
+ newLastChildren.forEach { lastChild ->
+ val wasLastChild = oldLastChildren.remove(lastChild)
+ if (!wasLastChild) {
+ val notAnimatedChild = !notificationRoundnessManager.isAnimatedChild(lastChild)
+ val animated = lastChild.isShown && notAnimatedChild
+ lastChild.requestBottomRoundness(1f, SECTION, animated)
+ }
+ }
+
+ // The Views left in the set are no longer in the first/last position
+ oldFirstChildren.forEach { noMoreFirstChild ->
+ noMoreFirstChild.requestTopRoundness(0f, SECTION)
+ }
+ oldLastChildren.forEach { noMoreLastChild ->
+ noMoreLastChild.requestBottomRoundness(0f, SECTION)
+ }
}
+
if (DEBUG) {
logSections(sections)
}
@@ -215,5 +260,6 @@ class NotificationSectionsManager @Inject internal constructor(
companion object {
private const val TAG = "NotifSectionsManager"
private const val DEBUG = false
+ private val SECTION = SourceType.from("Section")
}
}
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 b519aefcd4c9..21e2bd877bae 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
@@ -32,10 +32,12 @@ import android.animation.AnimatorListenerAdapter;
import android.animation.TimeAnimator;
import android.animation.ValueAnimator;
import android.annotation.ColorInt;
+import android.annotation.DrawableRes;
import android.annotation.FloatRange;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.StringRes;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
@@ -189,10 +191,13 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
private final boolean mDebugLines;
private Paint mDebugPaint;
- /** Used to track the Y positions that were already used to draw debug text labels. */
+ /**
+ * Used to track the Y positions that were already used to draw debug text labels.
+ */
private Set<Integer> mDebugTextUsedYPositions;
private final boolean mDebugRemoveAnimation;
private final boolean mSimplifiedAppearFraction;
+ private final boolean mUseRoundnessSourceTypes;
private int mContentHeight;
private float mIntrinsicContentHeight;
@@ -353,15 +358,14 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
private final Rect mQsHeaderBound = new Rect();
private boolean mContinuousShadowUpdate;
private boolean mContinuousBackgroundUpdate;
- private final ViewTreeObserver.OnPreDrawListener mShadowUpdater
- = () -> {
- updateViewShadows();
- return true;
- };
+ private final ViewTreeObserver.OnPreDrawListener mShadowUpdater = () -> {
+ updateViewShadows();
+ return true;
+ };
private final ViewTreeObserver.OnPreDrawListener mBackgroundUpdater = () -> {
- updateBackground();
- return true;
- };
+ updateBackground();
+ return true;
+ };
private final Comparator<ExpandableView> mViewPositionComparator = (view, otherView) -> {
float endY = view.getTranslationY() + view.getActualHeight();
float otherEndY = otherView.getTranslationY() + otherView.getActualHeight();
@@ -417,7 +421,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
*/
private int mMaxDisplayedNotifications = -1;
private float mKeyguardBottomPadding = -1;
- @VisibleForTesting int mStatusBarHeight;
+ @VisibleForTesting
+ int mStatusBarHeight;
private int mMinInteractionHeight;
private final Rect mClipRect = new Rect();
private boolean mIsClipped;
@@ -566,6 +571,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
@Nullable
private OnClickListener mManageButtonClickListener;
+ @Nullable
+ private OnNotificationRemovedListener mOnNotificationRemovedListener;
public NotificationStackScrollLayout(Context context, AttributeSet attrs) {
super(context, attrs, 0, 0);
@@ -574,6 +581,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
mDebugLines = featureFlags.isEnabled(Flags.NSSL_DEBUG_LINES);
mDebugRemoveAnimation = featureFlags.isEnabled(Flags.NSSL_DEBUG_REMOVE_ANIMATION);
mSimplifiedAppearFraction = featureFlags.isEnabled(Flags.SIMPLIFIED_APPEAR_FRACTION);
+ mUseRoundnessSourceTypes = featureFlags.isEnabled(Flags.USE_ROUNDNESS_SOURCETYPES);
mSectionsManager = Dependency.get(NotificationSectionsManager.class);
mScreenOffAnimationController =
Dependency.get(ScreenOffAnimationController.class);
@@ -739,7 +747,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
}
private void logHunSkippedForUnexpectedState(ExpandableNotificationRow enr,
- boolean expected, boolean actual) {
+ boolean expected, boolean actual) {
if (mLogger == null) return;
mLogger.hunSkippedForUnexpectedState(enr.getEntry(), expected, actual);
}
@@ -866,13 +874,13 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
/**
* Draws round rects for each background section.
- *
+ * <p>
* We want to draw a round rect for each background section as defined by {@link #mSections}.
* However, if two sections are directly adjacent with no gap between them (e.g. on the
* lockscreen where the shelf can appear directly below the high priority section, or while
* scrolling the shade so that the top of the shelf is right at the bottom of the high priority
* section), we don't want to round the adjacent corners.
- *
+ * <p>
* Since {@link Canvas} doesn't provide a way to draw a half-rounded rect, this means that we
* need to coalesce the backgrounds for adjacent sections and draw them as a single round rect.
* This method tracks the top of each rect we need to draw, then iterates through the visible
@@ -881,7 +889,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
* the current section. When we're done iterating we will always have one rect left to draw.
*/
private void drawBackgroundRects(Canvas canvas, int left, int right, int top,
- int animationYOffset) {
+ int animationYOffset) {
int backgroundRectTop = top;
int lastSectionBottom =
mSections[0].getCurrentBounds().bottom + animationYOffset;
@@ -972,7 +980,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
void initView(Context context, NotificationSwipeHelper swipeHelper,
- NotificationStackSizeCalculator notificationStackSizeCalculator) {
+ NotificationStackSizeCalculator notificationStackSizeCalculator) {
mScroller = new OverScroller(getContext());
mSwipeHelper = swipeHelper;
mNotificationStackSizeCalculator = notificationStackSizeCalculator;
@@ -1302,18 +1310,19 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
/**
* @return Whether we should skip stack height updates.
* True when
- * 1) Unlock hint is running
- * 2) Swiping up on lockscreen or flinging down after swipe up
+ * 1) Unlock hint is running
+ * 2) Swiping up on lockscreen or flinging down after swipe up
*/
private boolean shouldSkipHeightUpdate() {
return mAmbientState.isOnKeyguard()
&& (mAmbientState.isUnlockHintRunning()
- || mAmbientState.isSwipingUp()
- || mAmbientState.isFlingingAfterSwipeUpOnLockscreen());
+ || mAmbientState.isSwipingUp()
+ || mAmbientState.isFlingingAfterSwipeUpOnLockscreen());
}
/**
* Apply expansion fraction to the y position and height of the notifications panel.
+ *
* @param listenerNeedsAnimation does the listener need to animate?
*/
private void updateStackPosition(boolean listenerNeedsAnimation) {
@@ -1706,7 +1715,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
*/
@ShadeViewRefactor(RefactorComponent.COORDINATOR)
ExpandableView getChildAtPosition(float touchX, float touchY,
- boolean requireMinHeight, boolean ignoreDecors) {
+ boolean requireMinHeight, boolean ignoreDecors) {
// find the view under the pointer, accounting for GONE views
final int count = getChildCount();
for (int childIdx = 0; childIdx < count; childIdx++) {
@@ -1866,9 +1875,9 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
public void dismissViewAnimated(
View child, Consumer<Boolean> endRunnable, int delay, long duration) {
if (child instanceof SectionHeaderView) {
- ((StackScrollerDecorView) child).setContentVisible(
- false /* visible */, true /* animate */, endRunnable);
- return;
+ ((StackScrollerDecorView) child).setContentVisible(
+ false /* visible */, true /* animate */, endRunnable);
+ return;
}
mSwipeHelper.dismissChild(
child,
@@ -2030,10 +2039,11 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
/**
* Scrolls by the given delta, overscrolling if needed. If called during a fling and the delta
* would cause us to exceed the provided maximum overscroll, springs back instead.
- *
+ * <p>
* This method performs the determination of whether we're exceeding the overscroll and clamps
* the scroll amount if so. The actual scrolling/overscrolling happens in
* {@link #onCustomOverScrolled(int, boolean)}
+ *
* @param deltaY The (signed) number of pixels to scroll.
* @param scrollY The current scroll position (absolute scrolling only).
* @param scrollRangeY The maximum allowable scroll position (absolute scrolling only).
@@ -2096,7 +2106,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
*/
@ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
public void setOverScrollAmount(float amount, boolean onTop, boolean animate,
- boolean cancelAnimators) {
+ boolean cancelAnimators) {
setOverScrollAmount(amount, onTop, animate, cancelAnimators, isRubberbanded(onTop));
}
@@ -2112,7 +2122,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
*/
@ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
public void setOverScrollAmount(float amount, boolean onTop, boolean animate,
- boolean cancelAnimators, boolean isRubberbanded) {
+ boolean cancelAnimators, boolean isRubberbanded) {
if (cancelAnimators) {
mStateAnimator.cancelOverScrollAnimators(onTop);
}
@@ -2121,7 +2131,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
@ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
private void setOverScrollAmountInternal(float amount, boolean onTop, boolean animate,
- boolean isRubberbanded) {
+ boolean isRubberbanded) {
amount = Math.max(0, amount);
if (animate) {
mStateAnimator.animateOverScrollToAmount(amount, onTop, isRubberbanded);
@@ -2177,7 +2187,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
* Scrolls to the given position, overscrolling if needed. If called during a fling and the
* position exceeds the provided maximum overscroll, springs back instead.
*
- * @param scrollY The target scroll position.
+ * @param scrollY The target scroll position.
* @param clampedY Whether this value was clamped by the calling method, meaning we've reached
* the overscroll limit.
*/
@@ -2286,8 +2296,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
if (row.isSummaryWithChildren() && row.areChildrenExpanded()) {
List<ExpandableNotificationRow> notificationChildren =
row.getAttachedChildren();
- for (int childIndex = 0; childIndex < notificationChildren.size();
- childIndex++) {
+ int childrenSize = notificationChildren.size();
+ for (int childIndex = 0; childIndex < childrenSize; childIndex++) {
ExpandableNotificationRow rowChild = notificationChildren.get(childIndex);
if (rowChild.getTranslationY() + rowTranslation >= translationY) {
return rowChild;
@@ -2363,10 +2373,9 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
/**
* Calculate the gap height between two different views
*
- * @param previous the previousView
- * @param current the currentView
+ * @param previous the previousView
+ * @param current the currentView
* @param visibleIndex the visible index in the list
- *
* @return the gap height needed before the current view
*/
public float calculateGapHeight(
@@ -2374,7 +2383,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
ExpandableView current,
int visibleIndex
) {
- return mStackScrollAlgorithm.getGapHeightForChild(mSectionsManager, visibleIndex, current,
+ return mStackScrollAlgorithm.getGapHeightForChild(mSectionsManager, visibleIndex, current,
previous, mAmbientState.getFractionToShade(), mAmbientState.isOnKeyguard());
}
@@ -2640,15 +2649,15 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
return mScrolledToTopOnFirstDown
&& !mExpandedInThisMotion
&& (initialVelocity > mMinimumVelocity
- || (topOverScroll > mMinTopOverScrollToEscape && initialVelocity > 0));
+ || (topOverScroll > mMinTopOverScrollToEscape && initialVelocity > 0));
}
/**
* Updates the top padding of the notifications, taking {@link #getIntrinsicPadding()} into
* account.
*
- * @param qsHeight the top padding imposed by the quick settings panel
- * @param animate whether to animate the change
+ * @param qsHeight the top padding imposed by the quick settings panel
+ * @param animate whether to animate the change
*/
@ShadeViewRefactor(RefactorComponent.COORDINATOR)
public void updateTopPadding(float qsHeight, boolean animate) {
@@ -2722,21 +2731,34 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
}
-
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
public void setChildTransferInProgress(boolean childTransferInProgress) {
Assert.isMainThread();
mChildTransferInProgress = childTransferInProgress;
}
+ /**
+ * Set the remove notification listener
+ * @param listener callback for notification removed
+ */
+ public void setOnNotificationRemovedListener(OnNotificationRemovedListener listener) {
+ mOnNotificationRemovedListener = listener;
+ }
+
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
@Override
public void onViewRemoved(View child) {
super.onViewRemoved(child);
// we only call our internal methods if this is actually a removal and not just a
// notification which becomes a child notification
+ ExpandableView expandableView = (ExpandableView) child;
if (!mChildTransferInProgress) {
- onViewRemovedInternal((ExpandableView) child, this);
+ onViewRemovedInternal(expandableView, this);
+ }
+ if (mOnNotificationRemovedListener != null) {
+ mOnNotificationRemovedListener.onNotificationRemoved(
+ expandableView,
+ mChildTransferInProgress);
}
}
@@ -2996,8 +3018,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
mAnimateNextSectionBoundsChange = false;
}
mAmbientState.setLastVisibleBackgroundChild(lastChild);
- // TODO: Refactor SectionManager and put the RoundnessManager there.
- mController.getNotificationRoundnessManager().updateRoundedChildren(mSections);
+ if (!mUseRoundnessSourceTypes) {
+ // TODO: Refactor SectionManager and put the RoundnessManager there.
+ mController.getNotificationRoundnessManager().updateRoundedChildren(mSections);
+ }
mAnimateBottomOnLayout = false;
invalidate();
}
@@ -3204,7 +3228,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
// Only animate if we still have pinned heads up, otherwise we just have the
// regular collapse animation of the lock screen
|| (mKeyguardBypassEnabled && onKeyguard()
- && mInHeadsUpPinnedMode);
+ && mInHeadsUpPinnedMode);
if (performDisappearAnimation && !isHeadsUp) {
type = row.wasJustClicked()
? AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK
@@ -3349,7 +3373,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
AnimationEvent animEvent = duration == null
? new AnimationEvent(child, AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION)
: new AnimationEvent(
- child, AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION, duration);
+ child, AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION, duration);
mAnimationEvents.add(animEvent);
}
mChildrenChangingPositions.clear();
@@ -3440,7 +3464,9 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
@ShadeViewRefactor(RefactorComponent.LAYOUT_ALGORITHM)
protected StackScrollAlgorithm createStackScrollAlgorithm(Context context) {
- return new StackScrollAlgorithm(context, this);
+ StackScrollAlgorithm stackScrollAlgorithm = new StackScrollAlgorithm(context, this);
+ stackScrollAlgorithm.useRoundnessSourceTypes(mUseRoundnessSourceTypes);
+ return stackScrollAlgorithm;
}
/**
@@ -3770,7 +3796,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
}
private void logEmptySpaceClick(MotionEvent ev, boolean isTouchBelowLastNotification,
- int statusBarState, boolean touchIsClick) {
+ int statusBarState, boolean touchIsClick) {
if (mLogger == null) {
return;
}
@@ -3955,7 +3981,9 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
mOnEmptySpaceClickListener = listener;
}
- /** @hide */
+ /**
+ * @hide
+ */
@Override
@ShadeViewRefactor(RefactorComponent.INPUT)
public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
@@ -4556,7 +4584,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
}
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
- void setEmptyShadeView(EmptyShadeView emptyShadeView) {
+ public void setEmptyShadeView(EmptyShadeView emptyShadeView) {
int index = -1;
if (mEmptyShadeView != null) {
index = indexOfChild(mEmptyShadeView);
@@ -4567,15 +4595,43 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
}
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
- void updateEmptyShadeView(boolean visible, boolean areNotificationsHiddenInShade) {
+ void updateEmptyShadeView(
+ boolean visible, boolean areNotificationsHiddenInShade, boolean areSeenNotifsFiltered) {
mEmptyShadeView.setVisible(visible, mIsExpanded && mAnimationsEnabled);
+ if (areNotificationsHiddenInShade) {
+ updateEmptyShadeView(R.string.dnd_suppressing_shade_text, 0, 0);
+ } else if (areSeenNotifsFiltered) {
+ updateEmptyShadeView(
+ R.string.no_unseen_notif_text,
+ R.string.unlock_to_see_notif_text,
+ R.drawable.ic_friction_lock_closed);
+ } else {
+ updateEmptyShadeView(R.string.empty_shade_text, 0, 0);
+ }
+ }
+
+ private void updateEmptyShadeView(
+ @StringRes int newTextRes,
+ @StringRes int newFooterTextRes,
+ @DrawableRes int newFooterIconRes) {
int oldTextRes = mEmptyShadeView.getTextResource();
- int newTextRes = areNotificationsHiddenInShade
- ? R.string.dnd_suppressing_shade_text : R.string.empty_shade_text;
if (oldTextRes != newTextRes) {
mEmptyShadeView.setText(newTextRes);
}
+ int oldFooterTextRes = mEmptyShadeView.getFooterTextResource();
+ if (oldFooterTextRes != newFooterTextRes) {
+ mEmptyShadeView.setFooterText(newFooterTextRes);
+ }
+ int oldFooterIconRes = mEmptyShadeView.getFooterIconResource();
+ if (oldFooterIconRes != newFooterIconRes) {
+ mEmptyShadeView.setFooterIcon(newFooterIconRes);
+ }
+ if (newFooterIconRes != 0 || newFooterTextRes != 0) {
+ mEmptyShadeView.setFooterVisibility(View.VISIBLE);
+ } else {
+ mEmptyShadeView.setFooterVisibility(View.GONE);
+ }
}
public boolean isEmptyShadeViewVisible() {
@@ -4698,7 +4754,9 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
return touchY > mTopPadding + mStackTranslation;
}
- /** @hide */
+ /**
+ * @hide
+ */
@Override
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
@@ -5323,7 +5381,9 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
return canChildBeCleared(row) && matchesSelection(row, selection);
}
- /** Register a {@link View.OnClickListener} to be invoked when the Manage button is clicked. */
+ /**
+ * Register a {@link View.OnClickListener} to be invoked when the Manage button is clicked.
+ */
public void setManageButtonClickListener(@Nullable OnClickListener listener) {
mManageButtonClickListener = listener;
if (mFooterView != null) {
@@ -5348,9 +5408,9 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
private void inflateEmptyShadeView() {
+ EmptyShadeView oldView = mEmptyShadeView;
EmptyShadeView view = (EmptyShadeView) LayoutInflater.from(mContext).inflate(
R.layout.status_bar_no_notifications, this, false);
- view.setText(R.string.empty_shade_text);
view.setOnClickListener(v -> {
final boolean showHistory = mController.isHistoryEnabled();
Intent intent = showHistory
@@ -5359,6 +5419,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
mCentralSurfaces.startActivity(intent, true, true, Intent.FLAG_ACTIVITY_SINGLE_TOP);
});
setEmptyShadeView(view);
+ updateEmptyShadeView(
+ oldView == null ? R.string.empty_shade_text : oldView.getTextResource(),
+ oldView == null ? 0 : oldView.getFooterTextResource(),
+ oldView == null ? 0 : oldView.getFooterIconResource());
}
/**
@@ -5384,6 +5448,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
/**
* Set how far the wake up is when waking up from pulsing. This is a height and will adjust the
* notification positions accordingly.
+ *
* @param height the new wake up height
* @return the overflow how much the height is further than he lowest notification
*/
@@ -5615,7 +5680,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
* Set rounded rect clipping bounds on this view.
*/
public void setRoundedClippingBounds(int left, int top, int right, int bottom, int topRadius,
- int bottomRadius) {
+ int bottomRadius) {
if (mRoundedRectClippingLeft == left && mRoundedRectClippingRight == right
&& mRoundedRectClippingBottom == bottom && mRoundedRectClippingTop == top
&& mBgCornerRadii[0] == topRadius && mBgCornerRadii[5] == bottomRadius) {
@@ -5676,7 +5741,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
mLaunchingNotification = launching;
mLaunchingNotificationNeedsToBeClipped = mLaunchAnimationParams != null
&& (mLaunchAnimationParams.getStartRoundedTopClipping() > 0
- || mLaunchAnimationParams.getParentStartRoundedTopClipping() > 0);
+ || mLaunchAnimationParams.getParentStartRoundedTopClipping() > 0);
if (!mLaunchingNotificationNeedsToBeClipped || !mLaunchingNotification) {
mLaunchedNotificationClipPath.reset();
}
@@ -5714,7 +5779,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
mLaunchAnimationParams.getProgress(0,
NotificationLaunchAnimatorController.ANIMATION_DURATION_TOP_ROUNDING));
int top = (int) Math.min(MathUtils.lerp(mRoundedRectClippingTop,
- mLaunchAnimationParams.getTop(), expandProgress),
+ mLaunchAnimationParams.getTop(), expandProgress),
mRoundedRectClippingTop);
float topRadius = mLaunchAnimationParams.getTopCornerRadius();
float bottomRadius = mLaunchAnimationParams.getBottomCornerRadius();
@@ -5838,25 +5903,25 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
public interface OnOverscrollTopChangedListener {
- /**
- * Notifies a listener that the overscroll has changed.
- *
- * @param amount the amount of overscroll, in pixels
- * @param isRubberbanded if true, this is a rubberbanded overscroll; if false, this is an
- * unrubberbanded motion to directly expand overscroll view (e.g
- * expand
- * QS)
- */
- void onOverscrollTopChanged(float amount, boolean isRubberbanded);
+ /**
+ * Notifies a listener that the overscroll has changed.
+ *
+ * @param amount the amount of overscroll, in pixels
+ * @param isRubberbanded if true, this is a rubberbanded overscroll; if false, this is an
+ * unrubberbanded motion to directly expand overscroll view (e.g
+ * expand
+ * QS)
+ */
+ void onOverscrollTopChanged(float amount, boolean isRubberbanded);
- /**
- * Notify a listener that the scroller wants to escape from the scrolling motion and
- * start a fling animation to the expanded or collapsed overscroll view (e.g expand the QS)
- *
- * @param velocity The velocity that the Scroller had when over flinging
- * @param open Should the fling open or close the overscroll view.
- */
- void flingTopOverscroll(float velocity, boolean open);
+ /**
+ * Notify a listener that the scroller wants to escape from the scrolling motion and
+ * start a fling animation to the expanded or collapsed overscroll view (e.g expand the QS)
+ *
+ * @param velocity The velocity that the Scroller had when over flinging
+ * @param open Should the fling open or close the overscroll view.
+ */
+ void flingTopOverscroll(float velocity, boolean open);
}
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
@@ -6218,7 +6283,9 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
}
};
- public HeadsUpTouchHelper.Callback getHeadsUpCallback() { return mHeadsUpCallback; }
+ public HeadsUpTouchHelper.Callback getHeadsUpCallback() {
+ return mHeadsUpCallback;
+ }
void onGroupExpandChanged(ExpandableNotificationRow changedRow, boolean expanded) {
boolean animated = mAnimationsEnabled && (mIsExpanded || changedRow.isPinned());
@@ -6323,15 +6390,25 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
return mLastSentExpandedHeight;
}
- /** Enum for selecting some or all notification rows (does not included non-notif views). */
+ /**
+ * Enum for selecting some or all notification rows (does not included non-notif views).
+ */
@Retention(SOURCE)
@IntDef({ROWS_ALL, ROWS_HIGH_PRIORITY, ROWS_GENTLE})
- @interface SelectedRows {}
- /** All rows representing notifs. */
+ @interface SelectedRows {
+ }
+
+ /**
+ * All rows representing notifs.
+ */
public static final int ROWS_ALL = 0;
- /** Only rows where entry.isHighPriority() is true. */
+ /**
+ * Only rows where entry.isHighPriority() is true.
+ */
public static final int ROWS_HIGH_PRIORITY = 1;
- /** Only rows where entry.isHighPriority() is false. */
+ /**
+ * Only rows where entry.isHighPriority() is false.
+ */
public static final int ROWS_GENTLE = 2;
interface ClearAllListener {
@@ -6346,4 +6423,16 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
void onAnimationEnd(
List<ExpandableNotificationRow> viewsToRemove, @SelectedRows int selectedRows);
}
+
+ /**
+ *
+ */
+ public interface OnNotificationRemovedListener {
+ /**
+ *
+ * @param child
+ * @param isTransferInProgress
+ */
+ void onNotificationRemoved(ExpandableView child, boolean isTransferInProgress);
+ }
}
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 ad4501a75ebb..4bcc0b6923a1 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
@@ -81,6 +81,7 @@ import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.LaunchAnimationParameters;
+import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
import com.android.systemui.statusbar.notification.collection.NotifCollection;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
@@ -89,6 +90,7 @@ import com.android.systemui.statusbar.notification.collection.PipelineDumpable;
import com.android.systemui.statusbar.notification.collection.PipelineDumper;
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.provider.SeenNotificationsProvider;
import com.android.systemui.statusbar.notification.collection.provider.VisibilityLocationProviderDelegator;
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
import com.android.systemui.statusbar.notification.collection.render.NotifStackController;
@@ -174,14 +176,18 @@ public class NotificationStackScrollLayoutController {
private final StackStateLogger mStackStateLogger;
private final NotificationStackScrollLogger mLogger;
private final GroupExpansionManager mGroupExpansionManager;
+ private final NotifPipelineFlags mNotifPipelineFlags;
+ private final SeenNotificationsProvider mSeenNotificationsProvider;
private NotificationStackScrollLayout mView;
private boolean mFadeNotificationsOnDismiss;
private NotificationSwipeHelper mSwipeHelper;
- @Nullable private Boolean mHistoryEnabled;
+ @Nullable
+ private Boolean mHistoryEnabled;
private int mBarState;
private HeadsUpAppearanceController mHeadsUpAppearanceController;
private final FeatureFlags mFeatureFlags;
+ private final boolean mUseRoundnessSourceTypes;
private final NotificationTargetsHelper mNotificationTargetsHelper;
private View mLongPressedView;
@@ -387,7 +393,7 @@ public class NotificationStackScrollLayoutController {
if (item != null) {
Point origin = provider.getRevealAnimationOrigin();
mNotificationGutsManager.openGuts(row, origin.x, origin.y, item);
- } else {
+ } else {
Log.e(TAG, "Provider has shouldShowGutsOnSnapOpen, but provided no "
+ "menu item in menuItemtoExposeOnSnap. Skipping.");
}
@@ -416,7 +422,7 @@ public class NotificationStackScrollLayoutController {
@Override
public void onSnooze(StatusBarNotification sbn,
- NotificationSwipeActionHelper.SnoozeOption snoozeOption) {
+ NotificationSwipeActionHelper.SnoozeOption snoozeOption) {
mCentralSurfaces.setNotificationSnoozed(sbn, snoozeOption);
}
@@ -540,7 +546,7 @@ public class NotificationStackScrollLayoutController {
@Override
public boolean updateSwipeProgress(View animView, boolean dismissable,
- float swipeProgress) {
+ float swipeProgress) {
// Returning true prevents alpha fading.
return !mFadeNotificationsOnDismiss;
}
@@ -580,16 +586,22 @@ public class NotificationStackScrollLayoutController {
@Override
public void onHeadsUpPinned(NotificationEntry entry) {
- mNotificationRoundnessManager.updateView(entry.getRow(), false /* animate */);
+ if (!mUseRoundnessSourceTypes) {
+ mNotificationRoundnessManager.updateView(
+ entry.getRow(),
+ /* animate = */ false);
+ }
}
@Override
public void onHeadsUpUnPinned(NotificationEntry entry) {
- ExpandableNotificationRow row = entry.getRow();
- // update the roundedness posted, because we might be animating away the
- // headsup soon, so no need to set the roundedness to 0 and then back to 1.
- row.post(() -> mNotificationRoundnessManager.updateView(row,
- true /* animate */));
+ if (!mUseRoundnessSourceTypes) {
+ ExpandableNotificationRow row = entry.getRow();
+ // update the roundedness posted, because we might be animating away the
+ // headsup soon, so no need to set the roundedness to 0 and then back to 1.
+ row.post(() -> mNotificationRoundnessManager.updateView(row,
+ true /* animate */));
+ }
}
@Override
@@ -599,8 +611,10 @@ public class NotificationStackScrollLayoutController {
mView.setNumHeadsUp(numEntries);
mView.setTopHeadsUpEntry(topEntry);
generateHeadsUpAnimation(entry, isHeadsUp);
- ExpandableNotificationRow row = entry.getRow();
- mNotificationRoundnessManager.updateView(row, true /* animate */);
+ if (!mUseRoundnessSourceTypes) {
+ ExpandableNotificationRow row = entry.getRow();
+ mNotificationRoundnessManager.updateView(row, true /* animate */);
+ }
}
};
@@ -639,12 +653,14 @@ public class NotificationStackScrollLayoutController {
GroupExpansionManager groupManager,
@SilentHeader SectionHeaderController silentHeaderController,
NotifPipeline notifPipeline,
+ NotifPipelineFlags notifPipelineFlags,
NotifCollection notifCollection,
LockscreenShadeTransitionController lockscreenShadeTransitionController,
ShadeTransitionController shadeTransitionController,
UiEventLogger uiEventLogger,
NotificationRemoteInputManager remoteInputManager,
VisibilityLocationProviderDelegator visibilityLocationProviderDelegator,
+ SeenNotificationsProvider seenNotificationsProvider,
ShadeController shadeController,
InteractionJankMonitor jankMonitor,
StackStateLogger stackLogger,
@@ -683,12 +699,15 @@ public class NotificationStackScrollLayoutController {
mGroupExpansionManager = groupManager;
mSilentHeaderController = silentHeaderController;
mNotifPipeline = notifPipeline;
+ mNotifPipelineFlags = notifPipelineFlags;
mNotifCollection = notifCollection;
mUiEventLogger = uiEventLogger;
mRemoteInputManager = remoteInputManager;
mVisibilityLocationProviderDelegator = visibilityLocationProviderDelegator;
+ mSeenNotificationsProvider = seenNotificationsProvider;
mShadeController = shadeController;
mFeatureFlags = featureFlags;
+ mUseRoundnessSourceTypes = featureFlags.isEnabled(Flags.USE_ROUNDNESS_SOURCETYPES);
mNotificationTargetsHelper = notificationTargetsHelper;
updateResources();
}
@@ -755,8 +774,10 @@ public class NotificationStackScrollLayoutController {
mLockscreenUserManager.addUserChangedListener(mLockscreenUserChangeListener);
mFadeNotificationsOnDismiss = mFeatureFlags.isEnabled(Flags.NOTIFICATION_DISMISSAL_FADE);
- mNotificationRoundnessManager.setOnRoundingChangedCallback(mView::invalidate);
- mView.addOnExpandedHeightChangedListener(mNotificationRoundnessManager::setExpanded);
+ if (!mUseRoundnessSourceTypes) {
+ mNotificationRoundnessManager.setOnRoundingChangedCallback(mView::invalidate);
+ mView.addOnExpandedHeightChangedListener(mNotificationRoundnessManager::setExpanded);
+ }
mVisibilityLocationProviderDelegator.setDelegate(this::isInVisibleLocation);
@@ -975,7 +996,7 @@ public class NotificationStackScrollLayoutController {
}
public boolean isAddOrRemoveAnimationPending() {
- return mView.isAddOrRemoveAnimationPending();
+ return mView != null && mView.isAddOrRemoveAnimationPending();
}
public int getVisibleNotificationCount() {
@@ -989,9 +1010,11 @@ public class NotificationStackScrollLayoutController {
Log.wtf(TAG, "isHistoryEnabled failed to initialize its value");
return false;
}
- mHistoryEnabled = historyEnabled =
- Settings.Secure.getIntForUser(mView.getContext().getContentResolver(),
- Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 0, UserHandle.USER_CURRENT) == 1;
+ mHistoryEnabled = historyEnabled = Settings.Secure.getIntForUser(
+ mView.getContext().getContentResolver(),
+ Settings.Secure.NOTIFICATION_HISTORY_ENABLED,
+ 0,
+ UserHandle.USER_CURRENT) == 1;
}
return historyEnabled;
}
@@ -1021,7 +1044,7 @@ public class NotificationStackScrollLayoutController {
}
public void setOverScrollAmount(float amount, boolean onTop, boolean animate,
- boolean cancelAnimators) {
+ boolean cancelAnimators) {
mView.setOverScrollAmount(amount, onTop, animate, cancelAnimators);
}
@@ -1132,7 +1155,9 @@ public class NotificationStackScrollLayoutController {
}
public void setAlpha(float alpha) {
- mView.setAlpha(alpha);
+ if (mView != null) {
+ mView.setAlpha(alpha);
+ }
}
public float calculateAppearFraction(float height) {
@@ -1197,7 +1222,7 @@ public class NotificationStackScrollLayoutController {
/**
* Update whether we should show the empty shade view ("no notifications" in the shade).
- *
+ * <p>
* When in split mode, notifications are always visible regardless of the state of the
* QuickSettings panel. That being the case, empty view is always shown if the other conditions
* are true.
@@ -1212,14 +1237,18 @@ public class NotificationStackScrollLayoutController {
// For more details, see: b/228790482
&& !isInTransitionToKeyguard();
- mView.updateEmptyShadeView(shouldShow, mZenModeController.areNotificationsHiddenInShade());
+ mView.updateEmptyShadeView(
+ shouldShow,
+ mZenModeController.areNotificationsHiddenInShade(),
+ mNotifPipelineFlags.getShouldFilterUnseenNotifsOnKeyguard()
+ && mSeenNotificationsProvider.getHasFilteredOutSeenNotifications());
Trace.endSection();
}
/**
* @return true if {@link StatusBarStateController} is in transition to the KEYGUARD
- * and false otherwise.
+ * and false otherwise.
*/
private boolean isInTransitionToKeyguard() {
final int currentState = mStatusBarStateController.getState();
@@ -1251,7 +1280,9 @@ public class NotificationStackScrollLayoutController {
mView.setExpandedHeight(expandedHeight);
}
- /** Sets the QS header. Used to check if a touch is within its bounds. */
+ /**
+ * Sets the QS header. Used to check if a touch is within its bounds.
+ */
public void setQsHeader(ViewGroup view) {
mView.setQsHeader(view);
}
@@ -1314,7 +1345,7 @@ public class NotificationStackScrollLayoutController {
public RemoteInputController.Delegate createDelegate() {
return new RemoteInputController.Delegate() {
public void setRemoteInputActive(NotificationEntry entry,
- boolean remoteInputActive) {
+ boolean remoteInputActive) {
mHeadsUpManager.setRemoteInputActive(entry, remoteInputActive);
entry.notifyHeightChanged(true /* needsAnimation */);
updateFooter();
@@ -1445,7 +1476,7 @@ public class NotificationStackScrollLayoutController {
}
private void onAnimationEnd(List<ExpandableNotificationRow> viewsToRemove,
- @SelectedRows int selectedRows) {
+ @SelectedRows int selectedRows) {
if (selectedRows == ROWS_ALL) {
mNotifCollection.dismissAllNotifications(
mLockscreenUserManager.getCurrentUserId());
@@ -1488,8 +1519,8 @@ public class NotificationStackScrollLayoutController {
/**
* @return the inset during the full shade transition, that needs to be added to the position
- * of the quick settings edge. This is relevant for media, that is transitioning
- * from the keyguard host to the quick settings one.
+ * of the quick settings edge. This is relevant for media, that is transitioning
+ * from the keyguard host to the quick settings one.
*/
public int getFullShadeTransitionInset() {
MediaContainerView view = mKeyguardMediaController.getSinglePaneContainer();
@@ -1503,10 +1534,10 @@ public class NotificationStackScrollLayoutController {
/**
* @param fraction The fraction of lockscreen to shade transition.
* 0f for all other states.
- *
- * Once the lockscreen to shade transition completes and the shade is 100% open,
- * LockscreenShadeTransitionController resets amount and fraction to 0, where they remain
- * until the next lockscreen-to-shade transition.
+ * <p>
+ * Once the lockscreen to shade transition completes and the shade is 100% open,
+ * LockscreenShadeTransitionController resets amount and fraction to 0, where
+ * they remain until the next lockscreen-to-shade transition.
*/
public void setTransitionToFullShadeAmount(float fraction) {
mView.setFractionToShade(fraction);
@@ -1519,7 +1550,9 @@ public class NotificationStackScrollLayoutController {
mView.setExtraTopInsetForFullShadeTransition(overScrollAmount);
}
- /** */
+ /**
+ *
+ */
public void setWillExpand(boolean willExpand) {
mView.setWillExpand(willExpand);
}
@@ -1535,7 +1568,7 @@ public class NotificationStackScrollLayoutController {
* Set rounded rect clipping bounds on this view.
*/
public void setRoundedClippingBounds(int left, int top, int right, int bottom, int topRadius,
- int bottomRadius) {
+ int bottomRadius) {
mView.setRoundedClippingBounds(left, top, right, bottom, topRadius, bottomRadius);
}
@@ -1555,6 +1588,15 @@ public class NotificationStackScrollLayoutController {
}
/**
+ * Set the remove notification listener
+ * @param listener callback for notification removed
+ */
+ public void setOnNotificationRemovedListener(
+ NotificationStackScrollLayout.OnNotificationRemovedListener listener) {
+ mView.setOnNotificationRemovedListener(listener);
+ }
+
+ /**
* Enum for UiEvent logged from this class
*/
enum NotificationPanelEvent implements UiEventLogger.UiEventEnum {
@@ -1564,10 +1606,13 @@ public class NotificationStackScrollLayoutController {
@UiEvent(doc = "User dismissed all silent notifications from notification panel.")
DISMISS_SILENT_NOTIFICATIONS_PANEL(314);
private final int mId;
+
NotificationPanelEvent(int id) {
mId = id;
}
- @Override public int getId() {
+
+ @Override
+ public int getId() {
return mId;
}
@@ -1708,8 +1753,12 @@ public class NotificationStackScrollLayoutController {
@Override
public void bindRow(ExpandableNotificationRow row) {
row.setHeadsUpAnimatingAwayListener(animatingAway -> {
- mNotificationRoundnessManager.updateView(row, false);
- mHeadsUpAppearanceController.updateHeader(row.getEntry());
+ if (!mUseRoundnessSourceTypes) {
+ mNotificationRoundnessManager.updateView(row, false);
+ }
+ NotificationEntry entry = row.getEntry();
+ mHeadsUpAppearanceController.updateHeader(entry);
+ mHeadsUpAppearanceController.updateHeadsUpAndPulsingRoundness(entry);
});
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
index ee57411cb495..aaf9300e7cc8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
@@ -20,6 +20,7 @@ package com.android.systemui.statusbar.notification.stack;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_ROW_SWIPE;
import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.content.res.Resources;
import android.graphics.Rect;
@@ -34,9 +35,11 @@ import com.android.internal.jank.InteractionJankMonitor;
import com.android.systemui.SwipeHelper;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
+import com.android.systemui.statusbar.notification.SourceType;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
@@ -49,6 +52,7 @@ class NotificationSwipeHelper extends SwipeHelper implements NotificationSwipeAc
@VisibleForTesting
protected static final long COVER_MENU_DELAY = 4000;
private static final String TAG = "NotificationSwipeHelper";
+ private static final SourceType SWIPE_DISMISS = SourceType.from("SwipeDismiss");
private final Runnable mFalsingCheck;
private View mTranslatingParentView;
private View mMenuExposedView;
@@ -64,13 +68,21 @@ class NotificationSwipeHelper extends SwipeHelper implements NotificationSwipeAc
private WeakReference<NotificationMenuRowPlugin> mCurrMenuRowRef;
private boolean mIsExpanded;
private boolean mPulsing;
+ private final NotificationRoundnessManager mNotificationRoundnessManager;
+ private final boolean mUseRoundnessSourceTypes;
NotificationSwipeHelper(
- Resources resources, ViewConfiguration viewConfiguration,
- FalsingManager falsingManager, FeatureFlags featureFlags,
- int swipeDirection, NotificationCallback callback,
- NotificationMenuRowPlugin.OnMenuEventListener menuListener) {
+ Resources resources,
+ ViewConfiguration viewConfiguration,
+ FalsingManager falsingManager,
+ FeatureFlags featureFlags,
+ int swipeDirection,
+ NotificationCallback callback,
+ NotificationMenuRowPlugin.OnMenuEventListener menuListener,
+ NotificationRoundnessManager notificationRoundnessManager) {
super(swipeDirection, callback, resources, viewConfiguration, falsingManager, featureFlags);
+ mNotificationRoundnessManager = notificationRoundnessManager;
+ mUseRoundnessSourceTypes = featureFlags.isEnabled(Flags.USE_ROUNDNESS_SOURCETYPES);
mMenuListener = menuListener;
mCallback = callback;
mFalsingCheck = () -> resetExposedMenuView(true /* animate */, true /* force */);
@@ -304,6 +316,33 @@ class NotificationSwipeHelper extends SwipeHelper implements NotificationSwipeAc
handleMenuCoveredOrDismissed();
}
+ @Override
+ protected void prepareDismissAnimation(View view, Animator anim) {
+ super.prepareDismissAnimation(view, anim);
+
+ if (mUseRoundnessSourceTypes
+ && view instanceof ExpandableNotificationRow
+ && mNotificationRoundnessManager.isClearAllInProgress()) {
+ ExpandableNotificationRow row = (ExpandableNotificationRow) view;
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ row.requestRoundness(/* top = */ 1f, /* bottom = */ 1f, SWIPE_DISMISS);
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ row.requestRoundnessReset(SWIPE_DISMISS);
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ row.requestRoundnessReset(SWIPE_DISMISS);
+ }
+ });
+ }
+ }
+
@VisibleForTesting
protected void superDismissChild(final View view, float velocity, boolean useAccelerateInterpolator) {
super.dismissChild(view, velocity, useAccelerateInterpolator);
@@ -521,14 +560,17 @@ class NotificationSwipeHelper extends SwipeHelper implements NotificationSwipeAc
private int mSwipeDirection;
private NotificationCallback mNotificationCallback;
private NotificationMenuRowPlugin.OnMenuEventListener mOnMenuEventListener;
+ private NotificationRoundnessManager mNotificationRoundnessManager;
@Inject
Builder(@Main Resources resources, ViewConfiguration viewConfiguration,
- FalsingManager falsingManager, FeatureFlags featureFlags) {
+ FalsingManager falsingManager, FeatureFlags featureFlags,
+ NotificationRoundnessManager notificationRoundnessManager) {
mResources = resources;
mViewConfiguration = viewConfiguration;
mFalsingManager = falsingManager;
mFeatureFlags = featureFlags;
+ mNotificationRoundnessManager = notificationRoundnessManager;
}
Builder setSwipeDirection(int swipeDirection) {
@@ -549,7 +591,8 @@ class NotificationSwipeHelper extends SwipeHelper implements NotificationSwipeAc
NotificationSwipeHelper build() {
return new NotificationSwipeHelper(mResources, mViewConfiguration, mFalsingManager,
- mFeatureFlags, mSwipeDirection, mNotificationCallback, mOnMenuEventListener);
+ mFeatureFlags, mSwipeDirection, mNotificationCallback, mOnMenuEventListener,
+ mNotificationRoundnessManager);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelper.kt
index 991a14bb9c2a..548d1a135948 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelper.kt
@@ -20,8 +20,7 @@ class NotificationTargetsHelper
constructor(
featureFlags: FeatureFlags,
) {
- private val isNotificationGroupCornerEnabled =
- featureFlags.isEnabled(Flags.NOTIFICATION_GROUP_CORNER)
+ private val useRoundnessSourceTypes = featureFlags.isEnabled(Flags.USE_ROUNDNESS_SOURCETYPES)
/**
* This method looks for views that can be rounded (and implement [Roundable]) during a
@@ -48,7 +47,7 @@ constructor(
if (notificationParent != null && childrenContainer != null) {
// We are inside a notification group
- if (!isNotificationGroupCornerEnabled) {
+ if (!useRoundnessSourceTypes) {
return RoundableTargets(null, null, null)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index d8c68780951a..aff7b4c6c515 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -31,6 +31,7 @@ import com.android.systemui.R;
import com.android.systemui.animation.ShadeInterpolation;
import com.android.systemui.statusbar.EmptyShadeView;
import com.android.systemui.statusbar.NotificationShelf;
+import com.android.systemui.statusbar.notification.LegacySourceType;
import com.android.systemui.statusbar.notification.SourceType;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -51,6 +52,7 @@ public class StackScrollAlgorithm {
private static final String TAG = "StackScrollAlgorithm";
private static final Boolean DEBUG = false;
+ private static final SourceType STACK_SCROLL_ALGO = SourceType.from("StackScrollAlgorithm");
private final ViewGroup mHostView;
private float mPaddingBetweenElements;
@@ -70,6 +72,7 @@ public class StackScrollAlgorithm {
private float mQuickQsOffsetHeight;
private float mSmallCornerRadius;
private float mLargeCornerRadius;
+ private boolean mUseRoundnessSourceTypes;
public StackScrollAlgorithm(
Context context,
@@ -129,7 +132,7 @@ public class StackScrollAlgorithm {
}
private void updateAlphaState(StackScrollAlgorithmState algorithmState,
- AmbientState ambientState) {
+ AmbientState ambientState) {
for (ExpandableView view : algorithmState.visibleChildren) {
final ViewState viewState = view.getViewState();
@@ -229,7 +232,7 @@ public class StackScrollAlgorithm {
}
private void getNotificationChildrenStates(StackScrollAlgorithmState algorithmState,
- AmbientState ambientState) {
+ AmbientState ambientState) {
int childCount = algorithmState.visibleChildren.size();
for (int i = 0; i < childCount; i++) {
ExpandableView v = algorithmState.visibleChildren.get(i);
@@ -241,7 +244,7 @@ public class StackScrollAlgorithm {
}
private void updateSpeedBumpState(StackScrollAlgorithmState algorithmState,
- int speedBumpIndex) {
+ int speedBumpIndex) {
int childCount = algorithmState.visibleChildren.size();
int belowSpeedBump = speedBumpIndex;
for (int i = 0; i < childCount; i++) {
@@ -268,7 +271,7 @@ public class StackScrollAlgorithm {
}
private void updateClipping(StackScrollAlgorithmState algorithmState,
- AmbientState ambientState) {
+ AmbientState ambientState) {
float drawStart = ambientState.isOnKeyguard() ? 0
: ambientState.getStackY() - ambientState.getScrollY();
float clipStart = 0;
@@ -314,7 +317,7 @@ public class StackScrollAlgorithm {
* Updates the dimmed, activated and hiding sensitive states of the children.
*/
private void updateDimmedActivatedHideSensitive(AmbientState ambientState,
- StackScrollAlgorithmState algorithmState) {
+ StackScrollAlgorithmState algorithmState) {
boolean dimmed = ambientState.isDimmed();
boolean hideSensitive = ambientState.isHideSensitive();
View activatedChild = ambientState.getActivatedChild();
@@ -408,7 +411,7 @@ public class StackScrollAlgorithm {
}
private int updateNotGoneIndex(StackScrollAlgorithmState state, int notGoneIndex,
- ExpandableView v) {
+ ExpandableView v) {
ExpandableViewState viewState = v.getViewState();
viewState.notGoneIndex = notGoneIndex;
state.visibleChildren.add(v);
@@ -434,7 +437,7 @@ public class StackScrollAlgorithm {
* @param ambientState The current ambient state
*/
protected void updatePositionsForState(StackScrollAlgorithmState algorithmState,
- AmbientState ambientState) {
+ AmbientState ambientState) {
if (!ambientState.isOnKeyguard()
|| (ambientState.isBypassEnabled() && ambientState.isPulseExpanding())) {
algorithmState.mCurrentYPosition += mNotificationScrimPadding;
@@ -448,7 +451,7 @@ public class StackScrollAlgorithm {
}
private void setLocation(ExpandableViewState expandableViewState, float currentYPosition,
- int i) {
+ int i) {
expandableViewState.location = ExpandableViewState.LOCATION_MAIN_AREA;
if (currentYPosition <= 0) {
expandableViewState.location = ExpandableViewState.LOCATION_HIDDEN_TOP;
@@ -496,9 +499,13 @@ public class StackScrollAlgorithm {
}
@VisibleForTesting
- void maybeUpdateHeadsUpIsVisible(ExpandableViewState viewState, boolean isShadeExpanded,
- boolean mustStayOnScreen, boolean topVisible, float viewEnd, float hunMax) {
-
+ void maybeUpdateHeadsUpIsVisible(
+ ExpandableViewState viewState,
+ boolean isShadeExpanded,
+ boolean mustStayOnScreen,
+ boolean topVisible,
+ float viewEnd,
+ float hunMax) {
if (isShadeExpanded && mustStayOnScreen && topVisible) {
viewState.headsUpIsVisible = viewEnd < hunMax;
}
@@ -676,7 +683,7 @@ public class StackScrollAlgorithm {
}
private void updatePulsingStates(StackScrollAlgorithmState algorithmState,
- AmbientState ambientState) {
+ AmbientState ambientState) {
int childCount = algorithmState.visibleChildren.size();
for (int i = 0; i < childCount; i++) {
View child = algorithmState.visibleChildren.get(i);
@@ -693,7 +700,7 @@ public class StackScrollAlgorithm {
}
private void updateHeadsUpStates(StackScrollAlgorithmState algorithmState,
- AmbientState ambientState) {
+ AmbientState ambientState) {
int childCount = algorithmState.visibleChildren.size();
// Move the tracked heads up into position during the appear animation, by interpolating
@@ -777,7 +784,7 @@ public class StackScrollAlgorithm {
*/
@VisibleForTesting
void clampHunToTop(float quickQsOffsetHeight, float stackTranslation, float collapsedHeight,
- ExpandableViewState viewState) {
+ ExpandableViewState viewState) {
final float newTranslation = Math.max(quickQsOffsetHeight + stackTranslation,
viewState.getYTranslation());
@@ -792,7 +799,7 @@ public class StackScrollAlgorithm {
// Pin HUN to bottom of expanded QS
// while the rest of notifications are scrolled offscreen.
private void clampHunToMaxTranslation(AmbientState ambientState, ExpandableNotificationRow row,
- ExpandableViewState childState) {
+ ExpandableViewState childState) {
float maxHeadsUpTranslation = ambientState.getMaxHeadsUpTranslation();
final float maxShelfPosition = ambientState.getInnerHeight() + ambientState.getTopPadding()
+ ambientState.getStackTranslation();
@@ -807,14 +814,19 @@ public class StackScrollAlgorithm {
// Animate pinned HUN bottom corners to and from original roundness.
final float originalCornerRadius =
row.isLastInSection() ? 1f : (mSmallCornerRadius / mLargeCornerRadius);
- final float roundness = computeCornerRoundnessForPinnedHun(mHostView.getHeight(),
+ final float bottomValue = computeCornerRoundnessForPinnedHun(mHostView.getHeight(),
ambientState.getStackY(), getMaxAllowedChildHeight(row), originalCornerRadius);
- row.requestBottomRoundness(roundness, /* animate = */ false, SourceType.OnScroll);
+ if (mUseRoundnessSourceTypes) {
+ row.requestBottomRoundness(bottomValue, STACK_SCROLL_ALGO);
+ row.addOnDetachResetRoundness(STACK_SCROLL_ALGO);
+ } else {
+ row.requestBottomRoundness(bottomValue, LegacySourceType.OnScroll);
+ }
}
@VisibleForTesting
float computeCornerRoundnessForPinnedHun(float hostViewHeight, float stackY,
- float viewMaxHeight, float originalCornerRadius) {
+ float viewMaxHeight, float originalCornerRadius) {
// Compute y where corner roundness should be in its original unpinned state.
// We use view max height because the pinned collapsed HUN expands to max height
@@ -844,7 +856,7 @@ public class StackScrollAlgorithm {
* @param ambientState The ambient state of the algorithm
*/
private void updateZValuesForState(StackScrollAlgorithmState algorithmState,
- AmbientState ambientState) {
+ AmbientState ambientState) {
int childCount = algorithmState.visibleChildren.size();
float childrenOnTop = 0.0f;
@@ -876,9 +888,9 @@ public class StackScrollAlgorithm {
* previous HUNs whose Z positions are greater than 0.
*/
protected float updateChildZValue(int i, float childrenOnTop,
- StackScrollAlgorithmState algorithmState,
- AmbientState ambientState,
- boolean isTopHun) {
+ StackScrollAlgorithmState algorithmState,
+ AmbientState ambientState,
+ boolean isTopHun) {
ExpandableView child = algorithmState.visibleChildren.get(i);
ExpandableViewState childViewState = child.getViewState();
float baseZ = ambientState.getBaseZHeight();
@@ -950,6 +962,14 @@ public class StackScrollAlgorithm {
this.mIsExpanded = isExpanded;
}
+ /**
+ * Enable the support for rounded corner based on the SourceType
+ * @param enabled true if is supported
+ */
+ public void useRoundnessSourceTypes(boolean enabled) {
+ mUseRoundnessSourceTypes = enabled;
+ }
+
public static class StackScrollAlgorithmState {
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index 34e62ce321e0..03057a44ef90 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -496,7 +496,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp
&& mPendingAuthenticated.userId == KeyguardUpdateMonitor.getCurrentUser();
}
- public int getMode() {
+ public @WakeAndUnlockMode int getMode() {
return mMode;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
index e068f87f03b4..c7c644179e5b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -28,7 +28,6 @@ import android.os.Bundle;
import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
import android.view.KeyEvent;
-import android.view.MotionEvent;
import android.view.RemoteAnimationAdapter;
import android.view.View;
import android.view.ViewGroup;
@@ -285,7 +284,11 @@ public interface CentralSurfaces extends Dumpable, ActivityStarter, LifecycleOwn
void animateCollapseQuickSettings();
- void onTouchEvent(MotionEvent event);
+ /** */
+ boolean getCommandQueuePanelsEnabled();
+
+ /** */
+ int getStatusBarWindowState();
BiometricUnlockController getBiometricUnlockController();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 16112578de66..b394535ca011 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -93,7 +93,6 @@ import android.view.Display;
import android.view.IRemoteAnimationRunner;
import android.view.IWindowManager;
import android.view.KeyEvent;
-import android.view.MotionEvent;
import android.view.ThreadedRenderer;
import android.view.View;
import android.view.ViewGroup;
@@ -158,6 +157,8 @@ import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.keyguard.ui.binder.LightRevealScrimViewBinder;
+import com.android.systemui.keyguard.ui.viewmodel.LightRevealScrimViewModel;
import com.android.systemui.navigationbar.NavigationBarController;
import com.android.systemui.navigationbar.NavigationBarView;
import com.android.systemui.plugins.DarkIconDispatcher;
@@ -474,6 +475,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
private final OngoingCallController mOngoingCallController;
private final StatusBarSignalPolicy mStatusBarSignalPolicy;
private final StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager;
+ private final Lazy<LightRevealScrimViewModel> mLightRevealScrimViewModelLazy;
/** Controller for the Shade. */
@VisibleForTesting
@@ -740,7 +742,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
DeviceStateManager deviceStateManager,
WiredChargingRippleController wiredChargingRippleController,
IDreamManager dreamManager,
- Lazy<CameraLauncher> cameraLauncherLazy) {
+ Lazy<CameraLauncher> cameraLauncherLazy,
+ Lazy<LightRevealScrimViewModel> lightRevealScrimViewModelLazy) {
mContext = context;
mNotificationsController = notificationsController;
mFragmentService = fragmentService;
@@ -854,6 +857,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
deviceStateManager.registerCallback(mMainExecutor,
new FoldStateListener(mContext, this::onFoldedStateChanged));
wiredChargingRippleController.registerCallbacks();
+
+ mLightRevealScrimViewModelLazy = lightRevealScrimViewModelLazy;
}
@Override
@@ -983,6 +988,12 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
@Override
public void onKeyguardGoingAwayChanged() {
+ if (mFeatureFlags.isEnabled(Flags.LIGHT_REVEAL_MIGRATION)) {
+ // This code path is not used if the KeyguardTransitionRepository is managing
+ // the lightreveal scrim.
+ return;
+ }
+
// The light reveal scrim should always be fully revealed by the time the keyguard
// is done going away. Double check that this is true.
if (!mKeyguardStateController.isKeyguardGoingAway()) {
@@ -1219,6 +1230,12 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
mScrimController.attachViews(scrimBehind, notificationsScrim, scrimInFront);
mLightRevealScrim = mNotificationShadeWindowView.findViewById(R.id.light_reveal_scrim);
+
+ if (mFeatureFlags.isEnabled(Flags.LIGHT_REVEAL_MIGRATION)) {
+ LightRevealScrimViewBinder.bind(
+ mLightRevealScrim, mLightRevealScrimViewModelLazy.get());
+ }
+
mLightRevealScrim.setScrimOpaqueChangedListener((opaque) -> {
Runnable updateOpaqueness = () -> {
mNotificationShadeWindowController.setLightRevealScrimOpaque(
@@ -1993,43 +2010,14 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
}
}
- /** Called when a touch event occurred on {@link PhoneStatusBarView}. */
@Override
- public void onTouchEvent(MotionEvent event) {
- // TODO(b/202981994): Move this touch debugging to a central location. (Right now, it's
- // split between NotificationPanelViewController and here.)
- if (DEBUG_GESTURES) {
- if (event.getActionMasked() != MotionEvent.ACTION_MOVE) {
- EventLog.writeEvent(EventLogTags.SYSUI_STATUSBAR_TOUCH,
- event.getActionMasked(), (int) event.getX(), (int) event.getY(),
- mDisabled1, mDisabled2);
- }
-
- }
-
- if (SPEW) {
- Log.d(TAG, "Touch: rawY=" + event.getRawY() + " event=" + event + " mDisabled1="
- + mDisabled1 + " mDisabled2=" + mDisabled2);
- } else if (CHATTY) {
- if (event.getAction() != MotionEvent.ACTION_MOVE) {
- Log.d(TAG, String.format(
- "panel: %s at (%f, %f) mDisabled1=0x%08x mDisabled2=0x%08x",
- MotionEvent.actionToString(event.getAction()),
- event.getRawX(), event.getRawY(), mDisabled1, mDisabled2));
- }
- }
-
- if (DEBUG_GESTURES) {
- mGestureRec.add(event);
- }
+ public boolean getCommandQueuePanelsEnabled() {
+ return mCommandQueue.panelsEnabled();
+ }
- if (mStatusBarWindowState == WINDOW_STATE_SHOWING) {
- final boolean upOrCancel =
- event.getAction() == MotionEvent.ACTION_UP ||
- event.getAction() == MotionEvent.ACTION_CANCEL;
- setInteracting(StatusBarManager.WINDOW_STATUS_BAR,
- !upOrCancel || mShadeController.isExpandedVisible());
- }
+ @Override
+ public int getStatusBarWindowState() {
+ return mStatusBarWindowState;
}
@Override
@@ -3289,6 +3277,10 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
return;
}
+ if (mFeatureFlags.isEnabled(Flags.LIGHT_REVEAL_MIGRATION)) {
+ return;
+ }
+
final boolean wakingUpFromPowerButton = wakingUp
&& !(mLightRevealScrim.getRevealEffect() instanceof CircleReveal)
&& mWakefulnessLifecycle.getLastWakeReason()
@@ -4053,7 +4045,9 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
return;
}
- mLightRevealScrim.setAlpha(mScrimController.getState().getMaxLightRevealScrimAlpha());
+ if (!mFeatureFlags.isEnabled(Flags.LIGHT_REVEAL_MIGRATION)) {
+ mLightRevealScrim.setAlpha(mScrimController.getState().getMaxLightRevealScrimAlpha());
+ }
}
@Override
@@ -4234,6 +4228,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
@Override
public void onDozeAmountChanged(float linear, float eased) {
if (mFeatureFlags.isEnabled(Flags.LOCKSCREEN_ANIMATIONS)
+ && !mFeatureFlags.isEnabled(Flags.LIGHT_REVEAL_MIGRATION)
&& !(mLightRevealScrim.getRevealEffect() instanceof CircleReveal)) {
mLightRevealScrim.setRevealAmount(1f - linear);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
index 1169d3f21e28..c7be2193e9b5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
@@ -40,6 +40,8 @@ import com.android.systemui.statusbar.StatusIconDisplayable;
import com.android.systemui.statusbar.connectivity.ui.MobileContextProvider;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
+import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView;
+import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel;
import java.util.ArrayList;
import java.util.List;
@@ -50,20 +52,25 @@ public class DemoStatusIcons extends StatusIconContainer implements DemoMode, Da
private final LinearLayout mStatusIcons;
private final ArrayList<StatusBarMobileView> mMobileViews = new ArrayList<>();
+ private final ArrayList<ModernStatusBarMobileView> mModernMobileViews = new ArrayList<>();
private final int mIconSize;
private StatusBarWifiView mWifiView;
private boolean mDemoMode;
private int mColor;
+ private final MobileIconsViewModel mMobileIconsViewModel;
+
public DemoStatusIcons(
LinearLayout statusIcons,
+ MobileIconsViewModel mobileIconsViewModel,
int iconSize
) {
super(statusIcons.getContext());
mStatusIcons = statusIcons;
mIconSize = iconSize;
mColor = DarkIconDispatcher.DEFAULT_ICON_TINT;
+ mMobileIconsViewModel = mobileIconsViewModel;
if (statusIcons instanceof StatusIconContainer) {
setShouldRestrictIcons(((StatusIconContainer) statusIcons).isRestrictingIcons());
@@ -71,7 +78,7 @@ public class DemoStatusIcons extends StatusIconContainer implements DemoMode, Da
setShouldRestrictIcons(false);
}
setLayoutParams(mStatusIcons.getLayoutParams());
- setPadding(mStatusIcons.getPaddingLeft(),mStatusIcons.getPaddingTop(),
+ setPadding(mStatusIcons.getPaddingLeft(), mStatusIcons.getPaddingTop(),
mStatusIcons.getPaddingRight(), mStatusIcons.getPaddingBottom());
setOrientation(mStatusIcons.getOrientation());
setGravity(Gravity.CENTER_VERTICAL); // no LL.getGravity()
@@ -115,6 +122,8 @@ public class DemoStatusIcons extends StatusIconContainer implements DemoMode, Da
public void onDemoModeFinished() {
mDemoMode = false;
mStatusIcons.setVisibility(View.VISIBLE);
+ mModernMobileViews.clear();
+ mMobileViews.clear();
setVisibility(View.GONE);
}
@@ -269,6 +278,24 @@ public class DemoStatusIcons extends StatusIconContainer implements DemoMode, Da
}
/**
+ * Add a {@link ModernStatusBarMobileView}
+ * @param mobileContext possibly mcc/mnc overridden mobile context
+ * @param subId the subscriptionId for this mobile view
+ */
+ public void addModernMobileView(Context mobileContext, int subId) {
+ Log.d(TAG, "addModernMobileView (subId=" + subId + ")");
+ ModernStatusBarMobileView view = ModernStatusBarMobileView.constructAndBind(
+ mobileContext,
+ "mobile",
+ mMobileIconsViewModel.viewModelForSub(subId)
+ );
+
+ // mobile always goes at the end
+ mModernMobileViews.add(view);
+ addView(view, getChildCount(), createLayoutParams());
+ }
+
+ /**
* Apply an update to a mobile icon view for the given {@link MobileIconState}. For
* compatibility with {@link MobileContextProvider}, we have to recreate the view every time we
* update it, since the context (and thus the {@link Configuration}) may have changed
@@ -292,12 +319,19 @@ public class DemoStatusIcons extends StatusIconContainer implements DemoMode, Da
if (view.getSlot().equals("wifi")) {
removeView(mWifiView);
mWifiView = null;
- } else {
+ } else if (view instanceof StatusBarMobileView) {
StatusBarMobileView mobileView = matchingMobileView(view);
if (mobileView != null) {
removeView(mobileView);
mMobileViews.remove(mobileView);
}
+ } else if (view instanceof ModernStatusBarMobileView) {
+ ModernStatusBarMobileView mobileView = matchingModernMobileView(
+ (ModernStatusBarMobileView) view);
+ if (mobileView != null) {
+ removeView(mobileView);
+ mModernMobileViews.remove(mobileView);
+ }
}
}
@@ -316,6 +350,16 @@ public class DemoStatusIcons extends StatusIconContainer implements DemoMode, Da
return null;
}
+ private ModernStatusBarMobileView matchingModernMobileView(ModernStatusBarMobileView other) {
+ for (ModernStatusBarMobileView v : mModernMobileViews) {
+ if (v.getSubId() == other.getSubId()) {
+ return v;
+ }
+ }
+
+ return null;
+ }
+
private LayoutParams createLayoutParams() {
return new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, mIconSize);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
index 484441a1e76b..c217ab3b42e3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
@@ -19,11 +19,14 @@ package com.android.systemui.statusbar.phone;
import static com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentModule.OPERATOR_NAME_FRAME_VIEW;
import android.graphics.Rect;
+import android.util.MathUtils;
import android.view.View;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.widget.ViewClippingUtil;
import com.android.systemui.R;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shade.NotificationPanelViewController;
@@ -32,8 +35,10 @@ import com.android.systemui.statusbar.CrossFadeHelper;
import com.android.systemui.statusbar.HeadsUpStatusBarView;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
+import com.android.systemui.statusbar.notification.SourceType;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentScope;
import com.android.systemui.statusbar.policy.Clock;
@@ -51,6 +56,7 @@ import javax.inject.Named;
/**
* Controls the appearance of heads up notifications in the icon area and the header itself.
+ * It also controls the roundness of the heads up notifications and the pulsing notifications.
*/
@StatusBarFragmentScope
public class HeadsUpAppearanceController extends ViewController<HeadsUpStatusBarView>
@@ -59,12 +65,17 @@ public class HeadsUpAppearanceController extends ViewController<HeadsUpStatusBar
NotificationWakeUpCoordinator.WakeUpListener {
public static final int CONTENT_FADE_DURATION = 110;
public static final int CONTENT_FADE_DELAY = 100;
+
+ private static final SourceType HEADS_UP = SourceType.from("HeadsUp");
+ private static final SourceType PULSING = SourceType.from("Pulsing");
private final NotificationIconAreaController mNotificationIconAreaController;
private final HeadsUpManagerPhone mHeadsUpManager;
private final NotificationStackScrollLayoutController mStackScrollerController;
private final DarkIconDispatcher mDarkIconDispatcher;
private final NotificationPanelViewController mNotificationPanelViewController;
+ private final NotificationRoundnessManager mNotificationRoundnessManager;
+ private final boolean mUseRoundnessSourceTypes;
private final Consumer<ExpandableNotificationRow>
mSetTrackingHeadsUp = this::setTrackingHeadsUp;
private final BiConsumer<Float, Float> mSetExpandedHeight = this::setAppearFraction;
@@ -105,11 +116,15 @@ public class HeadsUpAppearanceController extends ViewController<HeadsUpStatusBar
CommandQueue commandQueue,
NotificationStackScrollLayoutController stackScrollerController,
NotificationPanelViewController notificationPanelViewController,
+ NotificationRoundnessManager notificationRoundnessManager,
+ FeatureFlags featureFlags,
HeadsUpStatusBarView headsUpStatusBarView,
Clock clockView,
@Named(OPERATOR_NAME_FRAME_VIEW) Optional<View> operatorNameViewOptional) {
super(headsUpStatusBarView);
mNotificationIconAreaController = notificationIconAreaController;
+ mNotificationRoundnessManager = notificationRoundnessManager;
+ mUseRoundnessSourceTypes = featureFlags.isEnabled(Flags.USE_ROUNDNESS_SOURCETYPES);
mHeadsUpManager = headsUpManager;
// We may be mid-HUN-expansion when this controller is re-created (for example, if the user
@@ -179,6 +194,7 @@ public class HeadsUpAppearanceController extends ViewController<HeadsUpStatusBar
public void onHeadsUpPinned(NotificationEntry entry) {
updateTopEntry();
updateHeader(entry);
+ updateHeadsUpAndPulsingRoundness(entry);
}
private void updateTopEntry() {
@@ -316,6 +332,7 @@ public class HeadsUpAppearanceController extends ViewController<HeadsUpStatusBar
public void onHeadsUpUnPinned(NotificationEntry entry) {
updateTopEntry();
updateHeader(entry);
+ updateHeadsUpAndPulsingRoundness(entry);
}
public void setAppearFraction(float expandedHeight, float appearFraction) {
@@ -346,7 +363,9 @@ public class HeadsUpAppearanceController extends ViewController<HeadsUpStatusBar
ExpandableNotificationRow previousTracked = mTrackedChild;
mTrackedChild = trackedChild;
if (previousTracked != null) {
- updateHeader(previousTracked.getEntry());
+ NotificationEntry entry = previousTracked.getEntry();
+ updateHeader(entry);
+ updateHeadsUpAndPulsingRoundness(entry);
}
}
@@ -357,6 +376,7 @@ public class HeadsUpAppearanceController extends ViewController<HeadsUpStatusBar
private void updateHeadsUpHeaders() {
mHeadsUpManager.getAllEntries().forEach(entry -> {
updateHeader(entry);
+ updateHeadsUpAndPulsingRoundness(entry);
});
}
@@ -370,6 +390,31 @@ public class HeadsUpAppearanceController extends ViewController<HeadsUpStatusBar
row.setHeaderVisibleAmount(headerVisibleAmount);
}
+ /**
+ * Update the HeadsUp and the Pulsing roundness based on current state
+ * @param entry target notification
+ */
+ public void updateHeadsUpAndPulsingRoundness(NotificationEntry entry) {
+ if (mUseRoundnessSourceTypes) {
+ ExpandableNotificationRow row = entry.getRow();
+ boolean isTrackedChild = row == mTrackedChild;
+ if (row.isPinned() || row.isHeadsUpAnimatingAway() || isTrackedChild) {
+ float roundness = MathUtils.saturate(1f - mAppearFraction);
+ row.requestRoundness(roundness, roundness, HEADS_UP);
+ } else {
+ row.requestRoundnessReset(HEADS_UP);
+ }
+ if (mNotificationRoundnessManager.shouldRoundNotificationPulsing()) {
+ if (row.showingPulsing()) {
+ row.requestRoundness(/* top = */ 1f, /* bottom = */ 1f, PULSING);
+ } else {
+ row.requestRoundnessReset(PULSING);
+ }
+ }
+ }
+ }
+
+
@Override
public void onDarkChanged(ArrayList<Rect> areas, float darkIntensity, int tint) {
mView.onDarkChanged(areas, darkIntensity, tint);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileController.java
index 4969a1c9a20b..6811bf6cce39 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileController.java
@@ -14,6 +14,8 @@
package com.android.systemui.statusbar.phone;
+import androidx.annotation.MainThread;
+
import com.android.systemui.statusbar.phone.ManagedProfileController.Callback;
import com.android.systemui.statusbar.policy.CallbackController;
@@ -25,8 +27,20 @@ public interface ManagedProfileController extends CallbackController<Callback> {
boolean isWorkModeEnabled();
- public interface Callback {
+ /**
+ * Callback to get updates about work profile status.
+ */
+ interface Callback {
+ /**
+ * Called when managed profile change is detected. This always runs on the main thread.
+ */
+ @MainThread
void onManagedProfileChanged();
+
+ /**
+ * Called when managed profile removal is detected. This always runs on the main thread.
+ */
+ @MainThread
void onManagedProfileRemoved();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
index 4beb87ddae2d..abdf8277e0c9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
@@ -33,31 +33,28 @@ import java.util.concurrent.Executor;
import javax.inject.Inject;
-/**
- */
@SysUISingleton
public class ManagedProfileControllerImpl implements ManagedProfileController {
private final List<Callback> mCallbacks = new ArrayList<>();
-
+ private final UserTrackerCallback mUserTrackerCallback = new UserTrackerCallback();
private final Context mContext;
private final Executor mMainExecutor;
private final UserManager mUserManager;
private final UserTracker mUserTracker;
private final LinkedList<UserInfo> mProfiles;
+
private boolean mListening;
private int mCurrentUser;
- /**
- */
@Inject
public ManagedProfileControllerImpl(Context context, @Main Executor mainExecutor,
- UserTracker userTracker) {
+ UserTracker userTracker, UserManager userManager) {
mContext = context;
mMainExecutor = mainExecutor;
- mUserManager = UserManager.get(mContext);
+ mUserManager = userManager;
mUserTracker = userTracker;
- mProfiles = new LinkedList<UserInfo>();
+ mProfiles = new LinkedList<>();
}
@Override
@@ -100,16 +97,22 @@ public class ManagedProfileControllerImpl implements ManagedProfileController {
}
}
if (mProfiles.size() == 0 && hadProfile && (user == mCurrentUser)) {
- for (Callback callback : mCallbacks) {
- callback.onManagedProfileRemoved();
- }
+ mMainExecutor.execute(this::notifyManagedProfileRemoved);
}
mCurrentUser = user;
}
}
+ private void notifyManagedProfileRemoved() {
+ for (Callback callback : mCallbacks) {
+ callback.onManagedProfileRemoved();
+ }
+ }
+
public boolean hasActiveProfile() {
- if (!mListening) reloadManagedProfiles();
+ if (!mListening || mUserTracker.getUserId() != mCurrentUser) {
+ reloadManagedProfiles();
+ }
synchronized (mProfiles) {
return mProfiles.size() > 0;
}
@@ -134,28 +137,28 @@ public class ManagedProfileControllerImpl implements ManagedProfileController {
mListening = listening;
if (listening) {
reloadManagedProfiles();
- mUserTracker.addCallback(mUserChangedCallback, mMainExecutor);
+ mUserTracker.addCallback(mUserTrackerCallback, mMainExecutor);
} else {
- mUserTracker.removeCallback(mUserChangedCallback);
+ mUserTracker.removeCallback(mUserTrackerCallback);
}
}
- private final UserTracker.Callback mUserChangedCallback =
- new UserTracker.Callback() {
- @Override
- public void onUserChanged(int newUser, @NonNull Context userContext) {
- reloadManagedProfiles();
- for (Callback callback : mCallbacks) {
- callback.onManagedProfileChanged();
- }
- }
+ private final class UserTrackerCallback implements UserTracker.Callback {
- @Override
- public void onProfilesChanged(@NonNull List<UserInfo> profiles) {
- reloadManagedProfiles();
- for (Callback callback : mCallbacks) {
- callback.onManagedProfileChanged();
- }
- }
- };
+ @Override
+ public void onUserChanged(int newUser, @NonNull Context userContext) {
+ reloadManagedProfiles();
+ for (Callback callback : mCallbacks) {
+ callback.onManagedProfileChanged();
+ }
+ }
+
+ @Override
+ public void onProfilesChanged(@NonNull List<UserInfo> profiles) {
+ reloadManagedProfiles();
+ for (Callback callback : mCallbacks) {
+ callback.onManagedProfileChanged();
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java
deleted file mode 100644
index 5e2a7c8ca540..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java
+++ /dev/null
@@ -1,171 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.phone;
-
-import static com.android.systemui.DejankUtils.whitelistIpcs;
-
-import android.content.Intent;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.view.View;
-import android.view.ViewGroup;
-
-import com.android.systemui.R;
-import com.android.systemui.animation.ActivityLaunchAnimator;
-import com.android.systemui.animation.Expandable;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
-import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.qs.FooterActionsView;
-import com.android.systemui.qs.dagger.QSScope;
-import com.android.systemui.qs.user.UserSwitchDialogController;
-import com.android.systemui.statusbar.policy.BaseUserSwitcherAdapter;
-import com.android.systemui.statusbar.policy.UserSwitcherController;
-import com.android.systemui.user.UserSwitcherActivity;
-import com.android.systemui.util.ViewController;
-
-import javax.inject.Inject;
-
-/** View Controller for {@link MultiUserSwitch}. */
-// TODO(b/242040009): Remove this file.
-public class MultiUserSwitchController extends ViewController<MultiUserSwitch> {
- private final UserManager mUserManager;
- private final UserSwitcherController mUserSwitcherController;
- private final FalsingManager mFalsingManager;
- private final UserSwitchDialogController mUserSwitchDialogController;
- private final ActivityStarter mActivityStarter;
- private final FeatureFlags mFeatureFlags;
-
- private BaseUserSwitcherAdapter mUserListener;
-
- private final View.OnClickListener mOnClickListener = new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
- return;
- }
-
- if (mFeatureFlags.isEnabled(Flags.FULL_SCREEN_USER_SWITCHER)) {
- Intent intent = new Intent(v.getContext(), UserSwitcherActivity.class);
- intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
-
- mActivityStarter.startActivity(intent, true /* dismissShade */,
- ActivityLaunchAnimator.Controller.fromView(v, null),
- true /* showOverlockscreenwhenlocked */, UserHandle.SYSTEM);
- } else {
- mUserSwitchDialogController.showDialog(v.getContext(), Expandable.fromView(v));
- }
- }
- };
-
- @QSScope
- public static class Factory {
- private final UserManager mUserManager;
- private final UserSwitcherController mUserSwitcherController;
- private final FalsingManager mFalsingManager;
- private final UserSwitchDialogController mUserSwitchDialogController;
- private final ActivityStarter mActivityStarter;
- private final FeatureFlags mFeatureFlags;
-
- @Inject
- public Factory(UserManager userManager, UserSwitcherController userSwitcherController,
- FalsingManager falsingManager,
- UserSwitchDialogController userSwitchDialogController, FeatureFlags featureFlags,
- ActivityStarter activityStarter) {
- mUserManager = userManager;
- mUserSwitcherController = userSwitcherController;
- mFalsingManager = falsingManager;
- mUserSwitchDialogController = userSwitchDialogController;
- mActivityStarter = activityStarter;
- mFeatureFlags = featureFlags;
- }
-
- public MultiUserSwitchController create(FooterActionsView view) {
- return new MultiUserSwitchController(view.findViewById(R.id.multi_user_switch),
- mUserManager, mUserSwitcherController,
- mFalsingManager, mUserSwitchDialogController, mFeatureFlags,
- mActivityStarter);
- }
- }
-
- private MultiUserSwitchController(MultiUserSwitch view, UserManager userManager,
- UserSwitcherController userSwitcherController,
- FalsingManager falsingManager, UserSwitchDialogController userSwitchDialogController,
- FeatureFlags featureFlags, ActivityStarter activityStarter) {
- super(view);
- mUserManager = userManager;
- mUserSwitcherController = userSwitcherController;
- mFalsingManager = falsingManager;
- mUserSwitchDialogController = userSwitchDialogController;
- mFeatureFlags = featureFlags;
- mActivityStarter = activityStarter;
- }
-
- @Override
- protected void onInit() {
- registerListener();
- mView.refreshContentDescription(getCurrentUser());
- }
-
- @Override
- protected void onViewAttached() {
- mView.setOnClickListener(mOnClickListener);
- }
-
- @Override
- protected void onViewDetached() {
- mView.setOnClickListener(null);
- }
-
- private void registerListener() {
- if (mUserManager.isUserSwitcherEnabled() && mUserListener == null) {
-
- final UserSwitcherController controller = mUserSwitcherController;
- if (controller != null) {
- mUserListener = new BaseUserSwitcherAdapter(controller) {
- @Override
- public void notifyDataSetChanged() {
- mView.refreshContentDescription(getCurrentUser());
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- return null;
- }
- };
- mView.refreshContentDescription(getCurrentUser());
- }
- }
- }
-
- private String getCurrentUser() {
- // TODO(b/138661450)
- if (whitelistIpcs(() -> mUserManager.isUserSwitcherEnabled())) {
- return mUserSwitcherController.getCurrentUserName();
- }
-
- return null;
- }
-
- /** Returns true if view should be made visible. */
- public boolean isMultiUserEnabled() {
- // TODO(b/138661450) Move IPC calls to background
- return whitelistIpcs(() -> mUserManager.isUserSwitcherEnabled(
- getResources().getBoolean(R.bool.qs_show_user_switcher_for_single_user)));
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
index a6c2b2c2771c..11bc490286f9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
@@ -15,14 +15,20 @@
*/
package com.android.systemui.statusbar.phone
+import android.app.StatusBarManager.WINDOW_STATE_SHOWING
+import android.app.StatusBarManager.WINDOW_STATUS_BAR
import android.content.res.Configuration
import android.graphics.Point
+import android.util.Log
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.view.ViewTreeObserver
import com.android.systemui.R
+import com.android.systemui.shade.ShadeController
+import com.android.systemui.shade.ShadeLogger
import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator
+import com.android.systemui.statusbar.phone.PhoneStatusBarView.TouchEventHandler
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.unfold.SysUIUnfoldComponent
import com.android.systemui.unfold.UNFOLD_STATUS_BAR
@@ -35,14 +41,18 @@ import java.util.Optional
import javax.inject.Inject
import javax.inject.Named
+private const val TAG = "PhoneStatusBarViewController"
+
/** Controller for [PhoneStatusBarView]. */
class PhoneStatusBarViewController private constructor(
view: PhoneStatusBarView,
@Named(UNFOLD_STATUS_BAR) private val progressProvider: ScopedUnfoldTransitionProgressProvider?,
+ private val centralSurfaces: CentralSurfaces,
+ private val shadeController: ShadeController,
+ private val shadeLogger: ShadeLogger,
private val moveFromCenterAnimationController: StatusBarMoveFromCenterAnimationController?,
private val userChipViewModel: StatusBarUserChipViewModel,
private val viewUtil: ViewUtil,
- touchEventHandler: PhoneStatusBarView.TouchEventHandler,
private val configurationController: ConfigurationController
) : ViewController<PhoneStatusBarView>(view) {
@@ -90,7 +100,7 @@ class PhoneStatusBarViewController private constructor(
}
init {
- mView.setTouchEventHandler(touchEventHandler)
+ mView.setTouchEventHandler(PhoneStatusBarViewTouchHandler())
mView.init(userChipViewModel)
}
@@ -120,6 +130,54 @@ class PhoneStatusBarViewController private constructor(
return viewUtil.touchIsWithinView(mView, x, y)
}
+ /** Called when a touch event occurred on {@link PhoneStatusBarView}. */
+ fun onTouchEvent(event: MotionEvent) {
+ if (centralSurfaces.statusBarWindowState == WINDOW_STATE_SHOWING) {
+ val upOrCancel =
+ event.action == MotionEvent.ACTION_UP ||
+ event.action == MotionEvent.ACTION_CANCEL
+ centralSurfaces.setInteracting(WINDOW_STATUS_BAR,
+ !upOrCancel || shadeController.isExpandedVisible)
+ }
+ }
+
+ inner class PhoneStatusBarViewTouchHandler : TouchEventHandler {
+ override fun onInterceptTouchEvent(event: MotionEvent) {
+ onTouchEvent(event)
+ }
+
+ override fun handleTouchEvent(event: MotionEvent): Boolean {
+ onTouchEvent(event)
+
+ // If panels aren't enabled, ignore the gesture and don't pass it down to the
+ // panel view.
+ if (!centralSurfaces.commandQueuePanelsEnabled) {
+ if (event.action == MotionEvent.ACTION_DOWN) {
+ Log.v(TAG, String.format("onTouchForwardedFromStatusBar: panel disabled, " +
+ "ignoring touch at (${event.x.toInt()},${event.y.toInt()})"))
+ }
+ return false
+ }
+
+ if (event.action == MotionEvent.ACTION_DOWN) {
+ // If the view that would receive the touch is disabled, just have status
+ // bar eat the gesture.
+ if (!centralSurfaces.notificationPanelViewController.isViewEnabled) {
+ shadeLogger.logMotionEvent(event,
+ "onTouchForwardedFromStatusBar: panel view disabled")
+ return true
+ }
+ if (centralSurfaces.notificationPanelViewController.isFullyCollapsed &&
+ event.y < 1f) {
+ // b/235889526 Eat events on the top edge of the phone when collapsed
+ shadeLogger.logMotionEvent(event, "top edge touch ignored")
+ return true
+ }
+ }
+ return centralSurfaces.notificationPanelViewController.sendTouchEventToView(event)
+ }
+ }
+
class StatusBarViewsCenterProvider : UnfoldMoveFromCenterAnimator.ViewCenterProvider {
override fun getViewCenter(view: View, outPoint: Point) =
when (view.id) {
@@ -157,20 +215,24 @@ class PhoneStatusBarViewController private constructor(
@Named(UNFOLD_STATUS_BAR)
private val progressProvider: Optional<ScopedUnfoldTransitionProgressProvider>,
private val userChipViewModel: StatusBarUserChipViewModel,
+ private val centralSurfaces: CentralSurfaces,
+ private val shadeController: ShadeController,
+ private val shadeLogger: ShadeLogger,
private val viewUtil: ViewUtil,
private val configurationController: ConfigurationController,
) {
fun create(
- view: PhoneStatusBarView,
- touchEventHandler: PhoneStatusBarView.TouchEventHandler
+ view: PhoneStatusBarView
) =
PhoneStatusBarViewController(
view,
progressProvider.getOrNull(),
+ centralSurfaces,
+ shadeController,
+ shadeLogger,
unfoldComponent.getOrNull()?.getStatusBarMoveFromCenterAnimationController(),
userChipViewModel,
viewUtil,
- touchEventHandler,
configurationController
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
index 0a0ded24ef30..df3ab493a4da 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -536,8 +536,7 @@ public interface StatusBarIconController {
mGroup.addView(view, index, onCreateLayoutParams());
if (mIsInDemoMode) {
- // TODO (b/249790009): demo mode should be handled at the data layer in the
- // new pipeline
+ mDemoStatusIcons.addModernMobileView(mContext, subId);
}
return view;
@@ -565,11 +564,13 @@ public interface StatusBarIconController {
private ModernStatusBarMobileView onCreateModernStatusBarMobileView(
String slot, int subId) {
+ Context mobileContext = mMobileContextProvider.getMobileContextForSub(subId, mContext);
return ModernStatusBarMobileView
.constructAndBind(
- mContext,
+ mobileContext,
slot,
- mMobileIconsViewModel.viewModelForSub(subId));
+ mMobileIconsViewModel.viewModelForSub(subId)
+ );
}
protected LinearLayout.LayoutParams onCreateLayoutParams() {
@@ -704,7 +705,7 @@ public interface StatusBarIconController {
}
protected DemoStatusIcons createDemoStatusIcons() {
- return new DemoStatusIcons((LinearLayout) mGroup, mIconSize);
+ return new DemoStatusIcons((LinearLayout) mGroup, mMobileIconsViewModel, mIconSize);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
index 674e5747e331..9fbe6cbc0e32 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
@@ -276,6 +276,11 @@ public class StatusBarIconControllerImpl implements Tunable,
String slotName = mContext.getString(com.android.internal.R.string.status_bar_mobile);
Slot mobileSlot = mStatusBarIconList.getSlot(slotName);
+ // Because of the way we cache the icon holders, we need to remove everything any time
+ // we get a new set of subscriptions. This might change in the future, but is required
+ // to support demo mode for now
+ removeAllIconsForSlot(slotName);
+
Collections.reverse(subIds);
for (Integer subId : subIds) {
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 f9d316b2ff20..aafcddd15077 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -19,9 +19,6 @@ package com.android.systemui.statusbar.phone;
import static android.view.WindowInsets.Type.navigationBars;
import static com.android.systemui.plugins.ActivityStarter.OnDismissAction;
-import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_DISMISS_BOUNCER;
-import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_SHOW_BOUNCER;
-import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_UNLOCK_COLLAPSING;
import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING;
@@ -140,6 +137,10 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
private final BouncerView mPrimaryBouncerView;
private final Lazy<com.android.systemui.shade.ShadeController> mShadeController;
+ // Local cache of expansion events, to avoid duplicates
+ private float mFraction = -1f;
+ private boolean mTracking = false;
+
private final PrimaryBouncerExpansionCallback mExpansionCallback =
new PrimaryBouncerExpansionCallback() {
private boolean mPrimaryBouncerAnimating;
@@ -440,80 +441,68 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
hideBouncer(true /* destroyView */);
}
- @Override
- public void onPanelExpansionChanged(ShadeExpansionChangeEvent event) {
- float fraction = event.getFraction();
- boolean tracking = event.getTracking();
+ private boolean beginShowingBouncer(ShadeExpansionChangeEvent event) {
// Avoid having the shade and the bouncer open at the same time over a dream.
final boolean hideBouncerOverDream =
mDreamOverlayStateController.isOverlayActive()
&& (mNotificationPanelViewController.isExpanded()
|| mNotificationPanelViewController.isExpanding());
- // We don't want to translate the bounce when:
- // • device is dozing and not pulsing
- // • Keyguard is occluded, because we're in a FLAG_SHOW_WHEN_LOCKED activity and need to
- // conserve the original animation.
- // • The user quickly taps on the display and we show "swipe up to unlock."
- // • Keyguard will be dismissed by an action. a.k.a: FLAG_DISMISS_KEYGUARD_ACTIVITY
- // • Full-screen user switcher is displayed.
- if (mDozing && !mPulsing) {
+ final boolean isUserTrackingStarted =
+ event.getFraction() != KeyguardBouncer.EXPANSION_HIDDEN && event.getTracking();
+
+ return mKeyguardStateController.isShowing()
+ && !primaryBouncerIsOrWillBeShowing()
+ && isUserTrackingStarted
+ && !hideBouncerOverDream
+ && !mKeyguardStateController.isOccluded()
+ && !mKeyguardStateController.canDismissLockScreen()
+ && !bouncerIsAnimatingAway()
+ && !mNotificationPanelViewController.isUnlockHintRunning()
+ && !(mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED);
+ }
+
+ @Override
+ public void onPanelExpansionChanged(ShadeExpansionChangeEvent event) {
+ float fraction = event.getFraction();
+ boolean tracking = event.getTracking();
+
+ if (mFraction == fraction && mTracking == tracking) {
+ // Ignore duplicate events, as they will cause confusion with bouncer expansion
return;
- } else if (mNotificationPanelViewController.isUnlockHintRunning()) {
+ }
+ mFraction = fraction;
+ mTracking = tracking;
+
+ /*
+ * The bouncer may have received a call to show(), or the following will infer it from
+ * device state and touch handling. The bouncer MUST have been notified that it is about to
+ * show if any subsequent events are to be handled.
+ */
+ if (beginShowingBouncer(event)) {
if (mPrimaryBouncer != null) {
- mPrimaryBouncer.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
+ mPrimaryBouncer.show(false /* resetSecuritySelection */, false /* scrimmed */);
} else {
- mPrimaryBouncerInteractor.setPanelExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
+ mPrimaryBouncerInteractor.show(/* isScrimmed= */false);
}
- } else if (mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED) {
- // Don't expand to the bouncer. Instead transition back to the lock screen (see
- // CentralSurfaces#showBouncerOrLockScreenIfKeyguard)
- return;
- } else if (mKeyguardStateController.isOccluded()
- && !mDreamOverlayStateController.isOverlayActive()) {
+ }
+
+ if (!primaryBouncerIsOrWillBeShowing()) {
return;
- } else if (needsFullscreenBouncer()) {
+ }
+
+ if (mKeyguardStateController.isShowing()) {
if (mPrimaryBouncer != null) {
- mPrimaryBouncer.setExpansion(KeyguardBouncer.EXPANSION_VISIBLE);
+ mPrimaryBouncer.setExpansion(fraction);
} else {
- mPrimaryBouncerInteractor.setPanelExpansion(KeyguardBouncer.EXPANSION_VISIBLE);
- }
- } else if (mKeyguardStateController.isShowing() && !hideBouncerOverDream) {
- if (!isWakeAndUnlocking()
- && !(mBiometricUnlockController.getMode() == MODE_DISMISS_BOUNCER)
- && !(mBiometricUnlockController.getMode() == MODE_SHOW_BOUNCER)
- && !isUnlockCollapsing()) {
- if (mPrimaryBouncer != null) {
- mPrimaryBouncer.setExpansion(fraction);
- } else {
- mPrimaryBouncerInteractor.setPanelExpansion(fraction);
- }
- }
- if (fraction != KeyguardBouncer.EXPANSION_HIDDEN && tracking
- && !mKeyguardStateController.canDismissLockScreen()
- && !primaryBouncerIsShowing()
- && !bouncerIsAnimatingAway()) {
- if (mPrimaryBouncer != null) {
- mPrimaryBouncer.show(false /* resetSecuritySelection */, false /* scrimmed */);
- } else {
- mPrimaryBouncerInteractor.show(/* isScrimmed= */false);
- }
+ mPrimaryBouncerInteractor.setPanelExpansion(fraction);
}
- } else if (!mKeyguardStateController.isShowing() && isPrimaryBouncerInTransit()) {
- // Keyguard is not visible anymore, but expansion animation was still running.
- // We need to hide the bouncer, otherwise it will be stuck in transit.
+ } else {
if (mPrimaryBouncer != null) {
mPrimaryBouncer.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
} else {
mPrimaryBouncerInteractor.setPanelExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
}
- } else if (mPulsing && fraction == KeyguardBouncer.EXPANSION_VISIBLE) {
- // Panel expanded while pulsing but didn't translate the bouncer (because we are
- // unlocked.) Let's simply wake-up to dismiss the lock screen.
- mCentralSurfaces.wakeUpIfDozing(
- SystemClock.uptimeMillis(),
- mCentralSurfaces.getBouncerContainer(),
- "BOUNCER_VISIBLE");
}
}
@@ -704,11 +693,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
return mode == MODE_WAKE_AND_UNLOCK || mode == MODE_WAKE_AND_UNLOCK_PULSING;
}
- private boolean isUnlockCollapsing() {
- int mode = mBiometricUnlockController.getMode();
- return mode == MODE_UNLOCK_COLLAPSING;
- }
-
/**
* Adds a {@param runnable} to be executed after Keyguard is gone.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
index efec27099dcd..730ecded58e2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
@@ -21,7 +21,6 @@ import android.view.View;
import com.android.systemui.R;
import com.android.systemui.battery.BatteryMeterView;
import com.android.systemui.dagger.qualifiers.RootView;
-import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.statusbar.HeadsUpStatusBarView;
import com.android.systemui.statusbar.phone.PhoneStatusBarTransitions;
import com.android.systemui.statusbar.phone.PhoneStatusBarView;
@@ -127,11 +126,9 @@ public interface StatusBarFragmentModule {
@StatusBarFragmentScope
static PhoneStatusBarViewController providePhoneStatusBarViewController(
PhoneStatusBarViewController.Factory phoneStatusBarViewControllerFactory,
- @RootView PhoneStatusBarView phoneStatusBarView,
- NotificationPanelViewController notificationPanelViewController) {
+ @RootView PhoneStatusBarView phoneStatusBarView) {
return phoneStatusBarViewControllerFactory.create(
- phoneStatusBarView,
- notificationPanelViewController.getStatusBarTouchEventHandler());
+ phoneStatusBarView);
}
/** */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
index fb67f1a1bf50..c350c78913d3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.pipeline.dagger
+import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.TableLogBufferFactory
@@ -24,11 +25,12 @@ import com.android.systemui.statusbar.pipeline.airplane.data.repository.Airplane
import com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel.AirplaneModeViewModel
import com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel.AirplaneModeViewModelImpl
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepositoryImpl
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileRepositorySwitcher
import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepositoryImpl
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractorImpl
+import com.android.systemui.statusbar.pipeline.mobile.ui.MobileUiAdapter
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxyImpl
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
@@ -40,6 +42,8 @@ import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiIntera
import dagger.Binds
import dagger.Module
import dagger.Provides
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
@Module
abstract class StatusBarPipelineModule {
@@ -52,26 +56,28 @@ abstract class StatusBarPipelineModule {
@Binds
abstract fun connectivityRepository(impl: ConnectivityRepositoryImpl): ConnectivityRepository
- @Binds
- abstract fun wifiRepository(impl: WifiRepositoryImpl): WifiRepository
+ @Binds abstract fun wifiRepository(impl: WifiRepositoryImpl): WifiRepository
@Binds
abstract fun wifiInteractor(impl: WifiInteractorImpl): WifiInteractor
@Binds
abstract fun mobileConnectionsRepository(
- impl: MobileConnectionsRepositoryImpl
+ impl: MobileRepositorySwitcher
): MobileConnectionsRepository
- @Binds
- abstract fun userSetupRepository(impl: UserSetupRepositoryImpl): UserSetupRepository
+ @Binds abstract fun userSetupRepository(impl: UserSetupRepositoryImpl): UserSetupRepository
- @Binds
- abstract fun mobileMappingsProxy(impl: MobileMappingsProxyImpl): MobileMappingsProxy
+ @Binds abstract fun mobileMappingsProxy(impl: MobileMappingsProxyImpl): MobileMappingsProxy
@Binds
abstract fun mobileIconsInteractor(impl: MobileIconsInteractorImpl): MobileIconsInteractor
+ @Binds
+ @IntoMap
+ @ClassKey(MobileUiAdapter::class)
+ abstract fun bindFeature(impl: MobileUiAdapter): CoreStartable
+
@Module
companion object {
@JvmStatic
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/DataConnectionState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/DataConnectionState.kt
index da87f7306e60..5479b92edd22 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/DataConnectionState.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/DataConnectionState.kt
@@ -20,6 +20,7 @@ import android.telephony.TelephonyManager.DATA_CONNECTED
import android.telephony.TelephonyManager.DATA_CONNECTING
import android.telephony.TelephonyManager.DATA_DISCONNECTED
import android.telephony.TelephonyManager.DATA_DISCONNECTING
+import android.telephony.TelephonyManager.DATA_UNKNOWN
import android.telephony.TelephonyManager.DataState
/** Internal enum representation of the telephony data connection states */
@@ -28,6 +29,7 @@ enum class DataConnectionState(@DataState val dataState: Int) {
Connecting(DATA_CONNECTING),
Disconnected(DATA_DISCONNECTED),
Disconnecting(DATA_DISCONNECTING),
+ Unknown(DATA_UNKNOWN),
}
fun @receiver:DataState Int.toDataConnectionType(): DataConnectionState =
@@ -36,5 +38,6 @@ fun @receiver:DataState Int.toDataConnectionType(): DataConnectionState =
DATA_CONNECTING -> DataConnectionState.Connecting
DATA_DISCONNECTED -> DataConnectionState.Disconnected
DATA_DISCONNECTING -> DataConnectionState.Disconnecting
- else -> throw IllegalArgumentException("unknown data state received")
+ DATA_UNKNOWN -> DataConnectionState.Unknown
+ else -> throw IllegalArgumentException("unknown data state received $this")
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileSubscriptionModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModel.kt
index 6341a114112c..1d00c330c420 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileSubscriptionModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModel.kt
@@ -27,7 +27,6 @@ import android.telephony.TelephonyCallback.ServiceStateListener
import android.telephony.TelephonyCallback.SignalStrengthsListener
import android.telephony.TelephonyDisplayInfo
import android.telephony.TelephonyManager
-import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState.Disconnected
/**
@@ -39,7 +38,7 @@ import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionS
* any new field that needs to be tracked should be copied into this data class rather than
* threading complex system objects through the pipeline.
*/
-data class MobileSubscriptionModel(
+data class MobileConnectionModel(
/** From [ServiceStateListener.onServiceStateChanged] */
val isEmergencyOnly: Boolean = false,
@@ -65,5 +64,5 @@ data class MobileSubscriptionModel(
* [resolvedNetworkType] is the [TelephonyDisplayInfo.getOverrideNetworkType] if it exists or
* [TelephonyDisplayInfo.getNetworkType]. This is used to look up the proper network type icon
*/
- val resolvedNetworkType: ResolvedNetworkType = DefaultNetworkType(NETWORK_TYPE_UNKNOWN),
+ val resolvedNetworkType: ResolvedNetworkType = ResolvedNetworkType.UnknownNetworkType,
)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/ResolvedNetworkType.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/ResolvedNetworkType.kt
index f385806c1b22..dd93541d7c8f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/ResolvedNetworkType.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/ResolvedNetworkType.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.pipeline.mobile.data.model
import android.telephony.Annotation.NetworkType
+import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
/**
@@ -26,8 +27,20 @@ import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
*/
sealed interface ResolvedNetworkType {
@NetworkType val type: Int
-}
+ val lookupKey: String
+
+ object UnknownNetworkType : ResolvedNetworkType {
+ override val type: Int = NETWORK_TYPE_UNKNOWN
+ override val lookupKey: String = "unknown"
+ }
-data class DefaultNetworkType(@NetworkType override val type: Int) : ResolvedNetworkType
+ data class DefaultNetworkType(
+ @NetworkType override val type: Int,
+ override val lookupKey: String,
+ ) : ResolvedNetworkType
-data class OverrideNetworkType(@NetworkType override val type: Int) : ResolvedNetworkType
+ data class OverrideNetworkType(
+ @NetworkType override val type: Int,
+ override val lookupKey: String,
+ ) : ResolvedNetworkType
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SubscriptionModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SubscriptionModel.kt
new file mode 100644
index 000000000000..2f34516285cf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SubscriptionModel.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.data.model
+
+/**
+ * SystemUI representation of [SubscriptionInfo]. Currently we only use two fields on the
+ * subscriptions themselves: subscriptionId and isOpportunistic. Any new fields that we need can be
+ * added below and provided in the repository classes
+ */
+data class SubscriptionModel(
+ val subscriptionId: Int,
+ /**
+ * True if the subscription that this model represents has [SubscriptionInfo.isOpportunistic].
+ * Opportunistic networks are networks with limited coverage, and we use this bit to determine
+ * filtering in certain cases. See [MobileIconsInteractor] for the filtering logic
+ */
+ val isOpportunistic: Boolean = false,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
index 581842bc2f57..2621f997d486 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
@@ -16,44 +16,13 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository
-import android.content.Context
-import android.database.ContentObserver
-import android.provider.Settings.Global
-import android.telephony.CellSignalStrength
-import android.telephony.CellSignalStrengthCdma
-import android.telephony.ServiceState
-import android.telephony.SignalStrength
import android.telephony.SubscriptionInfo
import android.telephony.SubscriptionManager
import android.telephony.TelephonyCallback
-import android.telephony.TelephonyDisplayInfo
-import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE
import android.telephony.TelephonyManager
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.statusbar.pipeline.mobile.data.model.DefaultNetworkType
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
-import com.android.systemui.statusbar.pipeline.mobile.data.model.OverrideNetworkType
-import com.android.systemui.statusbar.pipeline.mobile.data.model.toDataConnectionType
-import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
-import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange
-import com.android.systemui.util.settings.GlobalSettings
-import java.lang.IllegalStateException
-import javax.inject.Inject
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.asExecutor
-import kotlinx.coroutines.channels.awaitClose
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableSharedFlow
-import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.mapLatest
-import kotlinx.coroutines.flow.merge
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.flow.stateIn
/**
* Every mobile line of service can be identified via a [SubscriptionInfo] object. We set up a
@@ -67,11 +36,13 @@ import kotlinx.coroutines.flow.stateIn
* eventually becomes a single icon in the status bar.
*/
interface MobileConnectionRepository {
+ /** The subscriptionId that this connection represents */
+ val subId: Int
/**
* A flow that aggregates all necessary callbacks from [TelephonyCallback] into a single
* listener + model.
*/
- val subscriptionModelFlow: Flow<MobileSubscriptionModel>
+ val connectionInfo: Flow<MobileConnectionModel>
/** Observable tracking [TelephonyManager.isDataConnectionAllowed] */
val dataEnabled: StateFlow<Boolean>
/**
@@ -80,183 +51,3 @@ interface MobileConnectionRepository {
*/
val isDefaultDataSubscription: StateFlow<Boolean>
}
-
-@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
-@OptIn(ExperimentalCoroutinesApi::class)
-class MobileConnectionRepositoryImpl(
- private val context: Context,
- private val subId: Int,
- private val telephonyManager: TelephonyManager,
- private val globalSettings: GlobalSettings,
- defaultDataSubId: StateFlow<Int>,
- globalMobileDataSettingChangedEvent: Flow<Unit>,
- bgDispatcher: CoroutineDispatcher,
- logger: ConnectivityPipelineLogger,
- scope: CoroutineScope,
-) : MobileConnectionRepository {
- init {
- if (telephonyManager.subscriptionId != subId) {
- throw IllegalStateException(
- "TelephonyManager should be created with subId($subId). " +
- "Found ${telephonyManager.subscriptionId} instead."
- )
- }
- }
-
- private val telephonyCallbackEvent = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
-
- override val subscriptionModelFlow: StateFlow<MobileSubscriptionModel> = run {
- var state = MobileSubscriptionModel()
- conflatedCallbackFlow {
- // TODO (b/240569788): log all of these into the connectivity logger
- val callback =
- object :
- TelephonyCallback(),
- TelephonyCallback.ServiceStateListener,
- TelephonyCallback.SignalStrengthsListener,
- TelephonyCallback.DataConnectionStateListener,
- TelephonyCallback.DataActivityListener,
- TelephonyCallback.CarrierNetworkListener,
- TelephonyCallback.DisplayInfoListener {
- override fun onServiceStateChanged(serviceState: ServiceState) {
- state = state.copy(isEmergencyOnly = serviceState.isEmergencyOnly)
- trySend(state)
- }
-
- override fun onSignalStrengthsChanged(signalStrength: SignalStrength) {
- val cdmaLevel =
- signalStrength
- .getCellSignalStrengths(CellSignalStrengthCdma::class.java)
- .let { strengths ->
- if (!strengths.isEmpty()) {
- strengths[0].level
- } else {
- CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN
- }
- }
-
- val primaryLevel = signalStrength.level
-
- state =
- state.copy(
- cdmaLevel = cdmaLevel,
- primaryLevel = primaryLevel,
- isGsm = signalStrength.isGsm,
- )
- trySend(state)
- }
-
- override fun onDataConnectionStateChanged(
- dataState: Int,
- networkType: Int
- ) {
- state =
- state.copy(dataConnectionState = dataState.toDataConnectionType())
- trySend(state)
- }
-
- override fun onDataActivity(direction: Int) {
- state = state.copy(dataActivityDirection = direction)
- trySend(state)
- }
-
- override fun onCarrierNetworkChange(active: Boolean) {
- state = state.copy(carrierNetworkChangeActive = active)
- trySend(state)
- }
-
- override fun onDisplayInfoChanged(
- telephonyDisplayInfo: TelephonyDisplayInfo
- ) {
- val networkType =
- if (
- telephonyDisplayInfo.overrideNetworkType ==
- OVERRIDE_NETWORK_TYPE_NONE
- ) {
- DefaultNetworkType(telephonyDisplayInfo.networkType)
- } else {
- OverrideNetworkType(telephonyDisplayInfo.overrideNetworkType)
- }
- state = state.copy(resolvedNetworkType = networkType)
- trySend(state)
- }
- }
- telephonyManager.registerTelephonyCallback(bgDispatcher.asExecutor(), callback)
- awaitClose { telephonyManager.unregisterTelephonyCallback(callback) }
- }
- .onEach { telephonyCallbackEvent.tryEmit(Unit) }
- .logOutputChange(logger, "MobileSubscriptionModel")
- .stateIn(scope, SharingStarted.WhileSubscribed(), state)
- }
-
- /** Produces whenever the mobile data setting changes for this subId */
- private val localMobileDataSettingChangedEvent: Flow<Unit> = conflatedCallbackFlow {
- val observer =
- object : ContentObserver(null) {
- override fun onChange(selfChange: Boolean) {
- trySend(Unit)
- }
- }
-
- globalSettings.registerContentObserver(
- globalSettings.getUriFor("${Global.MOBILE_DATA}$subId"),
- /* notifyForDescendants */ true,
- observer
- )
-
- awaitClose { context.contentResolver.unregisterContentObserver(observer) }
- }
-
- /**
- * There are a few cases where we will need to poll [TelephonyManager] so we can update some
- * internal state where callbacks aren't provided. Any of those events should be merged into
- * this flow, which can be used to trigger the polling.
- */
- private val telephonyPollingEvent: Flow<Unit> =
- merge(
- telephonyCallbackEvent,
- localMobileDataSettingChangedEvent,
- globalMobileDataSettingChangedEvent,
- )
-
- override val dataEnabled: StateFlow<Boolean> =
- telephonyPollingEvent
- .mapLatest { dataConnectionAllowed() }
- .stateIn(scope, SharingStarted.WhileSubscribed(), dataConnectionAllowed())
-
- private fun dataConnectionAllowed(): Boolean = telephonyManager.isDataConnectionAllowed
-
- override val isDefaultDataSubscription: StateFlow<Boolean> =
- defaultDataSubId
- .mapLatest { it == subId }
- .stateIn(scope, SharingStarted.WhileSubscribed(), defaultDataSubId.value == subId)
-
- class Factory
- @Inject
- constructor(
- private val context: Context,
- private val telephonyManager: TelephonyManager,
- private val logger: ConnectivityPipelineLogger,
- private val globalSettings: GlobalSettings,
- @Background private val bgDispatcher: CoroutineDispatcher,
- @Application private val scope: CoroutineScope,
- ) {
- fun build(
- subId: Int,
- defaultDataSubId: StateFlow<Int>,
- globalMobileDataSettingChangedEvent: Flow<Unit>,
- ): MobileConnectionRepository {
- return MobileConnectionRepositoryImpl(
- context,
- subId,
- telephonyManager.createForSubscriptionId(subId),
- globalSettings,
- defaultDataSubId,
- globalMobileDataSettingChangedEvent,
- bgDispatcher,
- logger,
- scope,
- )
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt
index c3c1f1403c60..aea85eb020bd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt
@@ -16,53 +16,13 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository
-import android.annotation.SuppressLint
-import android.content.Context
-import android.content.IntentFilter
-import android.database.ContentObserver
-import android.net.ConnectivityManager
-import android.net.ConnectivityManager.NetworkCallback
-import android.net.Network
-import android.net.NetworkCapabilities
-import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
-import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
import android.provider.Settings
-import android.provider.Settings.Global.MOBILE_DATA
-import android.telephony.CarrierConfigManager
-import android.telephony.SubscriptionInfo
import android.telephony.SubscriptionManager
-import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
-import android.telephony.TelephonyCallback
-import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener
-import android.telephony.TelephonyManager
-import androidx.annotation.VisibleForTesting
-import com.android.internal.telephony.PhoneConstants
-import com.android.settingslib.mobile.MobileMappings
-import com.android.settingslib.mobile.MobileMappings.Config
-import com.android.systemui.broadcast.BroadcastDispatcher
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dagger.qualifiers.Background
+import com.android.settingslib.SignalIcon.MobileIconGroup
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
-import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
-import com.android.systemui.util.settings.GlobalSettings
-import javax.inject.Inject
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.asExecutor
-import kotlinx.coroutines.channels.awaitClose
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableSharedFlow
-import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.mapLatest
-import kotlinx.coroutines.flow.merge
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.withContext
/**
* Repo for monitoring the complete active subscription info list, to be consumed and filtered based
@@ -70,14 +30,11 @@ import kotlinx.coroutines.withContext
*/
interface MobileConnectionsRepository {
/** Observable list of current mobile subscriptions */
- val subscriptionsFlow: Flow<List<SubscriptionInfo>>
+ val subscriptions: StateFlow<List<SubscriptionModel>>
/** Observable for the subscriptionId of the current mobile data connection */
val activeMobileDataSubscriptionId: StateFlow<Int>
- /** Observable for [MobileMappings.Config] tracking the defaults */
- val defaultDataSubRatConfig: StateFlow<Config>
-
/** Tracks [SubscriptionManager.getDefaultDataSubscriptionId] */
val defaultDataSubId: StateFlow<Int>
@@ -89,203 +46,10 @@ interface MobileConnectionsRepository {
/** Observe changes to the [Settings.Global.MOBILE_DATA] setting */
val globalMobileDataSettingChangedEvent: Flow<Unit>
-}
-
-@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
-@OptIn(ExperimentalCoroutinesApi::class)
-@SysUISingleton
-class MobileConnectionsRepositoryImpl
-@Inject
-constructor(
- private val connectivityManager: ConnectivityManager,
- private val subscriptionManager: SubscriptionManager,
- private val telephonyManager: TelephonyManager,
- private val logger: ConnectivityPipelineLogger,
- broadcastDispatcher: BroadcastDispatcher,
- private val globalSettings: GlobalSettings,
- private val context: Context,
- @Background private val bgDispatcher: CoroutineDispatcher,
- @Application private val scope: CoroutineScope,
- private val mobileConnectionRepositoryFactory: MobileConnectionRepositoryImpl.Factory
-) : MobileConnectionsRepository {
- private val subIdRepositoryCache: MutableMap<Int, MobileConnectionRepository> = mutableMapOf()
-
- /**
- * State flow that emits the set of mobile data subscriptions, each represented by its own
- * [SubscriptionInfo]. We probably only need the [SubscriptionInfo.getSubscriptionId] of each
- * info object, but for now we keep track of the infos themselves.
- */
- override val subscriptionsFlow: StateFlow<List<SubscriptionInfo>> =
- conflatedCallbackFlow {
- val callback =
- object : SubscriptionManager.OnSubscriptionsChangedListener() {
- override fun onSubscriptionsChanged() {
- trySend(Unit)
- }
- }
-
- subscriptionManager.addOnSubscriptionsChangedListener(
- bgDispatcher.asExecutor(),
- callback,
- )
-
- awaitClose { subscriptionManager.removeOnSubscriptionsChangedListener(callback) }
- }
- .mapLatest { fetchSubscriptionsList() }
- .onEach { infos -> dropUnusedReposFromCache(infos) }
- .stateIn(scope, started = SharingStarted.WhileSubscribed(), listOf())
-
- /** StateFlow that keeps track of the current active mobile data subscription */
- override val activeMobileDataSubscriptionId: StateFlow<Int> =
- conflatedCallbackFlow {
- val callback =
- object : TelephonyCallback(), ActiveDataSubscriptionIdListener {
- override fun onActiveDataSubscriptionIdChanged(subId: Int) {
- trySend(subId)
- }
- }
-
- telephonyManager.registerTelephonyCallback(bgDispatcher.asExecutor(), callback)
- awaitClose { telephonyManager.unregisterTelephonyCallback(callback) }
- }
- .stateIn(scope, started = SharingStarted.WhileSubscribed(), INVALID_SUBSCRIPTION_ID)
-
- private val defaultDataSubIdChangeEvent: MutableSharedFlow<Unit> =
- MutableSharedFlow(extraBufferCapacity = 1)
-
- override val defaultDataSubId: StateFlow<Int> =
- broadcastDispatcher
- .broadcastFlow(
- IntentFilter(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)
- ) { intent, _ ->
- intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY, INVALID_SUBSCRIPTION_ID)
- }
- .distinctUntilChanged()
- .onEach { defaultDataSubIdChangeEvent.tryEmit(Unit) }
- .stateIn(
- scope,
- SharingStarted.WhileSubscribed(),
- SubscriptionManager.getDefaultDataSubscriptionId()
- )
-
- private val carrierConfigChangedEvent =
- broadcastDispatcher.broadcastFlow(
- IntentFilter(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)
- )
-
- /**
- * [Config] is an object that tracks relevant configuration flags for a given subscription ID.
- * In the case of [MobileMappings], it's hard-coded to check the default data subscription's
- * config, so this will apply to every icon that we care about.
- *
- * Relevant bits in the config are things like
- * [CarrierConfigManager.KEY_SHOW_4G_FOR_LTE_DATA_ICON_BOOL]
- *
- * This flow will produce whenever the default data subscription or the carrier config changes.
- */
- override val defaultDataSubRatConfig: StateFlow<Config> =
- merge(defaultDataSubIdChangeEvent, carrierConfigChangedEvent)
- .mapLatest { Config.readConfig(context) }
- .stateIn(
- scope,
- SharingStarted.WhileSubscribed(),
- initialValue = Config.readConfig(context)
- )
-
- override fun getRepoForSubId(subId: Int): MobileConnectionRepository {
- if (!isValidSubId(subId)) {
- throw IllegalArgumentException(
- "subscriptionId $subId is not in the list of valid subscriptions"
- )
- }
-
- return subIdRepositoryCache[subId]
- ?: createRepositoryForSubId(subId).also { subIdRepositoryCache[subId] = it }
- }
-
- /**
- * In single-SIM devices, the [MOBILE_DATA] setting is phone-wide. For multi-SIM, the individual
- * connection repositories also observe the URI for [MOBILE_DATA] + subId.
- */
- override val globalMobileDataSettingChangedEvent: Flow<Unit> = conflatedCallbackFlow {
- val observer =
- object : ContentObserver(null) {
- override fun onChange(selfChange: Boolean) {
- trySend(Unit)
- }
- }
-
- globalSettings.registerContentObserver(
- globalSettings.getUriFor(MOBILE_DATA),
- true,
- observer
- )
-
- awaitClose { context.contentResolver.unregisterContentObserver(observer) }
- }
-
- @SuppressLint("MissingPermission")
- override val defaultMobileNetworkConnectivity: StateFlow<MobileConnectivityModel> =
- conflatedCallbackFlow {
- val callback =
- object : NetworkCallback(FLAG_INCLUDE_LOCATION_INFO) {
- override fun onLost(network: Network) {
- // Send a disconnected model when lost. Maybe should create a sealed
- // type or null here?
- trySend(MobileConnectivityModel())
- }
-
- override fun onCapabilitiesChanged(
- network: Network,
- caps: NetworkCapabilities
- ) {
- trySend(
- MobileConnectivityModel(
- isConnected = caps.hasTransport(TRANSPORT_CELLULAR),
- isValidated = caps.hasCapability(NET_CAPABILITY_VALIDATED),
- )
- )
- }
- }
-
- connectivityManager.registerDefaultNetworkCallback(callback)
-
- awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
- }
- .stateIn(scope, SharingStarted.WhileSubscribed(), MobileConnectivityModel())
-
- private fun isValidSubId(subId: Int): Boolean {
- subscriptionsFlow.value.forEach {
- if (it.subscriptionId == subId) {
- return true
- }
- }
-
- return false
- }
-
- @VisibleForTesting fun getSubIdRepoCache() = subIdRepositoryCache
-
- private fun createRepositoryForSubId(subId: Int): MobileConnectionRepository {
- return mobileConnectionRepositoryFactory.build(
- subId,
- defaultDataSubId,
- globalMobileDataSettingChangedEvent,
- )
- }
-
- private fun dropUnusedReposFromCache(newInfos: List<SubscriptionInfo>) {
- // Remove any connection repository from the cache that isn't in the new set of IDs. They
- // will get garbage collected once their subscribers go away
- val currentValidSubscriptionIds = newInfos.map { it.subscriptionId }
- subIdRepositoryCache.keys.forEach {
- if (!currentValidSubscriptionIds.contains(it)) {
- subIdRepositoryCache.remove(it)
- }
- }
- }
+ /** The icon mapping from network type to [MobileIconGroup] for the default subscription */
+ val defaultMobileIconMapping: Flow<Map<String, MobileIconGroup>>
- private suspend fun fetchSubscriptionsList(): List<SubscriptionInfo> =
- withContext(bgDispatcher) { subscriptionManager.completeActiveSubscriptionInfoList }
+ /** Fallback [MobileIconGroup] in the case where there is no icon in the mapping */
+ val defaultMobileIconGroup: Flow<MobileIconGroup>
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt
new file mode 100644
index 000000000000..d8e0e81837c1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.data.repository
+
+import android.os.Bundle
+import androidx.annotation.VisibleForTesting
+import com.android.settingslib.SignalIcon
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.demomode.DemoMode
+import com.android.systemui.demomode.DemoModeController
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.DemoMobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileConnectionsRepositoryImpl
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.stateIn
+
+/**
+ * A provider for the [MobileConnectionsRepository] interface that can choose between the Demo and
+ * Prod concrete implementations at runtime. It works by defining a base flow, [activeRepo], which
+ * switches based on the latest information from [DemoModeController], and switches every flow in
+ * the interface to point to the currently-active provider. This allows us to put the demo mode
+ * interface in its own repository, completely separate from the real version, while still using all
+ * of the prod implementations for the rest of the pipeline (interactors and onward). Looks
+ * something like this:
+ *
+ * ```
+ * RealRepository
+ * │
+ * ├──►RepositorySwitcher──►RealInteractor──►RealViewModel
+ * │
+ * DemoRepository
+ * ```
+ *
+ * NOTE: because the UI layer for mobile icons relies on a nested-repository structure, it is likely
+ * that we will have to drain the subscription list whenever demo mode changes. Otherwise if a real
+ * subscription list [1] is replaced with a demo subscription list [1], the view models will not see
+ * a change (due to `distinctUntilChanged`) and will not refresh their data providers to the demo
+ * implementation.
+ */
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
+class MobileRepositorySwitcher
+@Inject
+constructor(
+ @Application scope: CoroutineScope,
+ val realRepository: MobileConnectionsRepositoryImpl,
+ val demoMobileConnectionsRepository: DemoMobileConnectionsRepository,
+ demoModeController: DemoModeController,
+) : MobileConnectionsRepository {
+
+ val isDemoMode: StateFlow<Boolean> =
+ conflatedCallbackFlow {
+ val callback =
+ object : DemoMode {
+ override fun dispatchDemoCommand(command: String?, args: Bundle?) {
+ // Nothing, we just care about on/off
+ }
+
+ override fun onDemoModeStarted() {
+ demoMobileConnectionsRepository.startProcessingCommands()
+ trySend(true)
+ }
+
+ override fun onDemoModeFinished() {
+ demoMobileConnectionsRepository.stopProcessingCommands()
+ trySend(false)
+ }
+ }
+
+ demoModeController.addCallback(callback)
+ awaitClose { demoModeController.removeCallback(callback) }
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), demoModeController.isInDemoMode)
+
+ // Convenient definition flow for the currently active repo (based on demo mode or not)
+ @VisibleForTesting
+ internal val activeRepo: StateFlow<MobileConnectionsRepository> =
+ isDemoMode
+ .mapLatest { demoMode ->
+ if (demoMode) {
+ demoMobileConnectionsRepository
+ } else {
+ realRepository
+ }
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), realRepository)
+
+ override val subscriptions: StateFlow<List<SubscriptionModel>> =
+ activeRepo
+ .flatMapLatest { it.subscriptions }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), realRepository.subscriptions.value)
+
+ override val activeMobileDataSubscriptionId: StateFlow<Int> =
+ activeRepo
+ .flatMapLatest { it.activeMobileDataSubscriptionId }
+ .stateIn(
+ scope,
+ SharingStarted.WhileSubscribed(),
+ realRepository.activeMobileDataSubscriptionId.value
+ )
+
+ override val defaultMobileIconMapping: Flow<Map<String, SignalIcon.MobileIconGroup>> =
+ activeRepo.flatMapLatest { it.defaultMobileIconMapping }
+
+ override val defaultMobileIconGroup: Flow<SignalIcon.MobileIconGroup> =
+ activeRepo.flatMapLatest { it.defaultMobileIconGroup }
+
+ override val defaultDataSubId: StateFlow<Int> =
+ activeRepo
+ .flatMapLatest { it.defaultDataSubId }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), realRepository.defaultDataSubId.value)
+
+ override val defaultMobileNetworkConnectivity: StateFlow<MobileConnectivityModel> =
+ activeRepo
+ .flatMapLatest { it.defaultMobileNetworkConnectivity }
+ .stateIn(
+ scope,
+ SharingStarted.WhileSubscribed(),
+ realRepository.defaultMobileNetworkConnectivity.value
+ )
+
+ override val globalMobileDataSettingChangedEvent: Flow<Unit> =
+ activeRepo.flatMapLatest { it.globalMobileDataSettingChangedEvent }
+
+ override fun getRepoForSubId(subId: Int): MobileConnectionRepository {
+ if (isDemoMode.value) {
+ return demoMobileConnectionsRepository.getRepoForSubId(subId)
+ }
+ return realRepository.getRepoForSubId(subId)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
new file mode 100644
index 000000000000..1e7fae717a2d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
@@ -0,0 +1,262 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.data.repository.demo
+
+import android.content.Context
+import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
+import android.util.Log
+import com.android.settingslib.SignalIcon
+import com.android.settingslib.mobile.MobileMappings
+import com.android.settingslib.mobile.TelephonyIcons
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.DefaultNetworkType
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel.Mobile
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel.MobileDisabled
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
+
+/** This repository vends out data based on demo mode commands */
+@OptIn(ExperimentalCoroutinesApi::class)
+class DemoMobileConnectionsRepository
+@Inject
+constructor(
+ private val dataSource: DemoModeMobileConnectionDataSource,
+ @Application private val scope: CoroutineScope,
+ context: Context,
+) : MobileConnectionsRepository {
+
+ private var demoCommandJob: Job? = null
+
+ private var connectionRepoCache = mutableMapOf<Int, DemoMobileConnectionRepository>()
+ private val subscriptionInfoCache = mutableMapOf<Int, SubscriptionModel>()
+ val demoModeFinishedEvent = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
+
+ private val _subscriptions = MutableStateFlow<List<SubscriptionModel>>(listOf())
+ override val subscriptions =
+ _subscriptions
+ .onEach { infos -> dropUnusedReposFromCache(infos) }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), _subscriptions.value)
+
+ private fun dropUnusedReposFromCache(newInfos: List<SubscriptionModel>) {
+ // Remove any connection repository from the cache that isn't in the new set of IDs. They
+ // will get garbage collected once their subscribers go away
+ val currentValidSubscriptionIds = newInfos.map { it.subscriptionId }
+
+ connectionRepoCache =
+ connectionRepoCache
+ .filter { currentValidSubscriptionIds.contains(it.key) }
+ .toMutableMap()
+ }
+
+ private fun maybeCreateSubscription(subId: Int) {
+ if (!subscriptionInfoCache.containsKey(subId)) {
+ SubscriptionModel(subscriptionId = subId, isOpportunistic = false).also {
+ subscriptionInfoCache[subId] = it
+ }
+
+ _subscriptions.value = subscriptionInfoCache.values.toList()
+ }
+ }
+
+ // TODO(b/261029387): add a command for this value
+ override val activeMobileDataSubscriptionId =
+ subscriptions
+ .mapLatest { infos ->
+ // For now, active is just the first in the list
+ infos.firstOrNull()?.subscriptionId ?: INVALID_SUBSCRIPTION_ID
+ }
+ .stateIn(
+ scope,
+ SharingStarted.WhileSubscribed(),
+ subscriptions.value.firstOrNull()?.subscriptionId ?: INVALID_SUBSCRIPTION_ID
+ )
+
+ /** Demo mode doesn't currently support modifications to the mobile mappings */
+ val defaultDataSubRatConfig = MutableStateFlow(MobileMappings.Config.readConfig(context))
+
+ override val defaultMobileIconGroup = flowOf(TelephonyIcons.THREE_G)
+
+ override val defaultMobileIconMapping = MutableStateFlow(TelephonyIcons.ICON_NAME_TO_ICON)
+
+ /**
+ * In order to maintain compatibility with the old demo mode shell command API, reverse the
+ * [MobileMappings] lookup from (NetworkType: String -> Icon: MobileIconGroup), so that we can
+ * parse the string from the command line into a preferred icon group, and send _a_ valid
+ * network type for that icon through the pipeline.
+ *
+ * Note: collisions don't matter here, because the data source (the command line) only cares
+ * about the resulting icon, not the underlying network type.
+ */
+ private val mobileMappingsReverseLookup: StateFlow<Map<SignalIcon.MobileIconGroup, String>> =
+ defaultMobileIconMapping
+ .mapLatest { networkToIconMap -> networkToIconMap.reverse() }
+ .stateIn(
+ scope,
+ SharingStarted.WhileSubscribed(),
+ defaultMobileIconMapping.value.reverse()
+ )
+
+ private fun <K, V> Map<K, V>.reverse() = entries.associateBy({ it.value }) { it.key }
+
+ // TODO(b/261029387): add a command for this value
+ override val defaultDataSubId =
+ activeMobileDataSubscriptionId.stateIn(
+ scope,
+ SharingStarted.WhileSubscribed(),
+ INVALID_SUBSCRIPTION_ID
+ )
+
+ // TODO(b/261029387): not yet supported
+ override val defaultMobileNetworkConnectivity = MutableStateFlow(MobileConnectivityModel())
+
+ override fun getRepoForSubId(subId: Int): DemoMobileConnectionRepository {
+ return connectionRepoCache[subId]
+ ?: DemoMobileConnectionRepository(subId).also { connectionRepoCache[subId] = it }
+ }
+
+ override val globalMobileDataSettingChangedEvent = MutableStateFlow(Unit)
+
+ fun startProcessingCommands() {
+ demoCommandJob =
+ scope.launch {
+ dataSource.mobileEvents.filterNotNull().collect { event -> processEvent(event) }
+ }
+ }
+
+ fun stopProcessingCommands() {
+ demoCommandJob?.cancel()
+ _subscriptions.value = listOf()
+ connectionRepoCache.clear()
+ subscriptionInfoCache.clear()
+ }
+
+ private fun processEvent(event: FakeNetworkEventModel) {
+ when (event) {
+ is Mobile -> {
+ processEnabledMobileState(event)
+ }
+ is MobileDisabled -> {
+ processDisabledMobileState(event)
+ }
+ }
+ }
+
+ private fun processEnabledMobileState(state: Mobile) {
+ // get or create the connection repo, and set its values
+ val subId = state.subId ?: DEFAULT_SUB_ID
+ maybeCreateSubscription(subId)
+
+ val connection = getRepoForSubId(subId)
+ // This is always true here, because we split out disabled states at the data-source level
+ connection.dataEnabled.value = true
+ connection.isDefaultDataSubscription.value = state.dataType != null
+
+ connection.connectionInfo.value = state.toMobileConnectionModel()
+ }
+
+ private fun processDisabledMobileState(state: MobileDisabled) {
+ if (_subscriptions.value.isEmpty()) {
+ // Nothing to do here
+ return
+ }
+
+ val subId =
+ state.subId
+ ?: run {
+ // For sake of usability, we can allow for no subId arg if there is only one
+ // subscription
+ if (_subscriptions.value.size > 1) {
+ Log.d(
+ TAG,
+ "processDisabledMobileState: Unable to infer subscription to " +
+ "disable. Specify subId using '-e slot <subId>'" +
+ "Known subIds: [${subIdsString()}]"
+ )
+ return
+ }
+
+ // Use the only existing subscription as our arg, since there is only one
+ _subscriptions.value[0].subscriptionId
+ }
+
+ removeSubscription(subId)
+ }
+
+ private fun removeSubscription(subId: Int) {
+ val currentSubscriptions = _subscriptions.value
+ subscriptionInfoCache.remove(subId)
+ _subscriptions.value = currentSubscriptions.filter { it.subscriptionId != subId }
+ }
+
+ private fun subIdsString(): String =
+ _subscriptions.value.joinToString(",") { it.subscriptionId.toString() }
+
+ private fun Mobile.toMobileConnectionModel(): MobileConnectionModel {
+ return MobileConnectionModel(
+ isEmergencyOnly = false, // TODO(b/261029387): not yet supported
+ isGsm = false, // TODO(b/261029387): not yet supported
+ cdmaLevel = level ?: 0,
+ primaryLevel = level ?: 0,
+ dataConnectionState =
+ DataConnectionState.Connected, // TODO(b/261029387): not yet supported
+ dataActivityDirection = activity,
+ carrierNetworkChangeActive = carrierNetworkChange,
+ resolvedNetworkType = dataType.toResolvedNetworkType()
+ )
+ }
+
+ private fun SignalIcon.MobileIconGroup?.toResolvedNetworkType(): ResolvedNetworkType {
+ val key = mobileMappingsReverseLookup.value[this] ?: "dis"
+ return DefaultNetworkType(DEMO_NET_TYPE, key)
+ }
+
+ companion object {
+ private const val TAG = "DemoMobileConnectionsRepo"
+
+ private const val DEFAULT_SUB_ID = 1
+
+ private const val DEMO_NET_TYPE = 1234
+ }
+}
+
+class DemoMobileConnectionRepository(override val subId: Int) : MobileConnectionRepository {
+ override val connectionInfo = MutableStateFlow(MobileConnectionModel())
+
+ override val dataEnabled = MutableStateFlow(true)
+
+ override val isDefaultDataSubscription = MutableStateFlow(true)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoModeMobileConnectionDataSource.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoModeMobileConnectionDataSource.kt
new file mode 100644
index 000000000000..da55787e8fed
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoModeMobileConnectionDataSource.kt
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.data.repository.demo
+
+import android.os.Bundle
+import android.telephony.Annotation.DataActivityType
+import android.telephony.TelephonyManager.DATA_ACTIVITY_IN
+import android.telephony.TelephonyManager.DATA_ACTIVITY_INOUT
+import android.telephony.TelephonyManager.DATA_ACTIVITY_NONE
+import android.telephony.TelephonyManager.DATA_ACTIVITY_OUT
+import com.android.settingslib.SignalIcon.MobileIconGroup
+import com.android.settingslib.mobile.TelephonyIcons
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.demomode.DemoMode
+import com.android.systemui.demomode.DemoMode.COMMAND_NETWORK
+import com.android.systemui.demomode.DemoModeController
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel.Mobile
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel.MobileDisabled
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.shareIn
+
+/**
+ * Data source that can map from demo mode commands to inputs into the
+ * [DemoMobileConnectionsRepository]'s flows
+ */
+@SysUISingleton
+class DemoModeMobileConnectionDataSource
+@Inject
+constructor(
+ demoModeController: DemoModeController,
+ @Application scope: CoroutineScope,
+) {
+ private val demoCommandStream: Flow<Bundle> = conflatedCallbackFlow {
+ val callback =
+ object : DemoMode {
+ override fun demoCommands(): List<String> = listOf(COMMAND_NETWORK)
+
+ override fun dispatchDemoCommand(command: String, args: Bundle) {
+ trySend(args)
+ }
+
+ override fun onDemoModeFinished() {
+ // Handled elsewhere
+ }
+
+ override fun onDemoModeStarted() {
+ // Handled elsewhere
+ }
+ }
+
+ demoModeController.addCallback(callback)
+ awaitClose { demoModeController.removeCallback(callback) }
+ }
+
+ // If the args contains "mobile", then all of the args are relevant. It's just the way demo mode
+ // commands work and it's a little silly
+ private val _mobileCommands = demoCommandStream.map { args -> args.toMobileEvent() }
+ val mobileEvents = _mobileCommands.shareIn(scope, SharingStarted.WhileSubscribed())
+
+ private fun Bundle.toMobileEvent(): FakeNetworkEventModel? {
+ val mobile = getString("mobile") ?: return null
+ return if (mobile == "show") {
+ activeMobileEvent()
+ } else {
+ MobileDisabled(subId = getString("slot")?.toInt())
+ }
+ }
+
+ /** Parse a valid mobile command string into a network event */
+ private fun Bundle.activeMobileEvent(): Mobile {
+ // There are many key/value pairs supported by mobile demo mode. Bear with me here
+ val level = getString("level")?.toInt()
+ val dataType = getString("datatype")?.toDataType()
+ val slot = getString("slot")?.toInt()
+ val carrierId = getString("carrierid")?.toInt()
+ val inflateStrength = getString("inflate")?.toBoolean()
+ val activity = getString("activity")?.toActivity()
+ val carrierNetworkChange = getString("carriernetworkchange") == "show"
+
+ return Mobile(
+ level = level,
+ dataType = dataType,
+ subId = slot,
+ carrierId = carrierId,
+ inflateStrength = inflateStrength,
+ activity = activity,
+ carrierNetworkChange = carrierNetworkChange,
+ )
+ }
+}
+
+private fun String.toDataType(): MobileIconGroup =
+ when (this) {
+ "1x" -> TelephonyIcons.ONE_X
+ "3g" -> TelephonyIcons.THREE_G
+ "4g" -> TelephonyIcons.FOUR_G
+ "4g+" -> TelephonyIcons.FOUR_G_PLUS
+ "5g" -> TelephonyIcons.NR_5G
+ "5ge" -> TelephonyIcons.LTE_CA_5G_E
+ "5g+" -> TelephonyIcons.NR_5G_PLUS
+ "e" -> TelephonyIcons.E
+ "g" -> TelephonyIcons.G
+ "h" -> TelephonyIcons.H
+ "h+" -> TelephonyIcons.H_PLUS
+ "lte" -> TelephonyIcons.LTE
+ "lte+" -> TelephonyIcons.LTE_PLUS
+ "dis" -> TelephonyIcons.DATA_DISABLED
+ "not" -> TelephonyIcons.NOT_DEFAULT_DATA
+ else -> TelephonyIcons.UNKNOWN
+ }
+
+@DataActivityType
+private fun String.toActivity(): Int =
+ when (this) {
+ "inout" -> DATA_ACTIVITY_INOUT
+ "in" -> DATA_ACTIVITY_IN
+ "out" -> DATA_ACTIVITY_OUT
+ else -> DATA_ACTIVITY_NONE
+ }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/model/FakeNetworkEventModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/model/FakeNetworkEventModel.kt
new file mode 100644
index 000000000000..3f3acafd2d1c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/model/FakeNetworkEventModel.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model
+
+import android.telephony.Annotation.DataActivityType
+import com.android.settingslib.SignalIcon
+
+/**
+ * Model for the demo commands, ported from [NetworkControllerImpl]
+ *
+ * Nullable fields represent optional command line arguments
+ */
+sealed interface FakeNetworkEventModel {
+ data class Mobile(
+ val level: Int?,
+ val dataType: SignalIcon.MobileIconGroup?,
+ // Null means the default (chosen by the repository)
+ val subId: Int?,
+ val carrierId: Int?,
+ val inflateStrength: Boolean?,
+ @DataActivityType val activity: Int?,
+ val carrierNetworkChange: Boolean,
+ ) : FakeNetworkEventModel
+
+ data class MobileDisabled(
+ // Null means the default (chosen by the repository)
+ val subId: Int?
+ ) : FakeNetworkEventModel
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
new file mode 100644
index 000000000000..15505fd3d9e5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
@@ -0,0 +1,253 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod
+
+import android.content.Context
+import android.database.ContentObserver
+import android.provider.Settings.Global
+import android.telephony.CellSignalStrength
+import android.telephony.CellSignalStrengthCdma
+import android.telephony.ServiceState
+import android.telephony.SignalStrength
+import android.telephony.TelephonyCallback
+import android.telephony.TelephonyDisplayInfo
+import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE
+import android.telephony.TelephonyManager
+import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.DefaultNetworkType
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.OverrideNetworkType
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.UnknownNetworkType
+import com.android.systemui.statusbar.pipeline.mobile.data.model.toDataConnectionType
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
+import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange
+import com.android.systemui.util.settings.GlobalSettings
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.asExecutor
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.stateIn
+
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
+class MobileConnectionRepositoryImpl(
+ private val context: Context,
+ override val subId: Int,
+ private val telephonyManager: TelephonyManager,
+ private val globalSettings: GlobalSettings,
+ defaultDataSubId: StateFlow<Int>,
+ globalMobileDataSettingChangedEvent: Flow<Unit>,
+ mobileMappingsProxy: MobileMappingsProxy,
+ bgDispatcher: CoroutineDispatcher,
+ logger: ConnectivityPipelineLogger,
+ scope: CoroutineScope,
+) : MobileConnectionRepository {
+ init {
+ if (telephonyManager.subscriptionId != subId) {
+ throw IllegalStateException(
+ "TelephonyManager should be created with subId($subId). " +
+ "Found ${telephonyManager.subscriptionId} instead."
+ )
+ }
+ }
+
+ private val telephonyCallbackEvent = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
+
+ override val connectionInfo: StateFlow<MobileConnectionModel> = run {
+ var state = MobileConnectionModel()
+ conflatedCallbackFlow {
+ // TODO (b/240569788): log all of these into the connectivity logger
+ val callback =
+ object :
+ TelephonyCallback(),
+ TelephonyCallback.ServiceStateListener,
+ TelephonyCallback.SignalStrengthsListener,
+ TelephonyCallback.DataConnectionStateListener,
+ TelephonyCallback.DataActivityListener,
+ TelephonyCallback.CarrierNetworkListener,
+ TelephonyCallback.DisplayInfoListener {
+ override fun onServiceStateChanged(serviceState: ServiceState) {
+ state = state.copy(isEmergencyOnly = serviceState.isEmergencyOnly)
+ trySend(state)
+ }
+
+ override fun onSignalStrengthsChanged(signalStrength: SignalStrength) {
+ val cdmaLevel =
+ signalStrength
+ .getCellSignalStrengths(CellSignalStrengthCdma::class.java)
+ .let { strengths ->
+ if (!strengths.isEmpty()) {
+ strengths[0].level
+ } else {
+ CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN
+ }
+ }
+
+ val primaryLevel = signalStrength.level
+
+ state =
+ state.copy(
+ cdmaLevel = cdmaLevel,
+ primaryLevel = primaryLevel,
+ isGsm = signalStrength.isGsm,
+ )
+ trySend(state)
+ }
+
+ override fun onDataConnectionStateChanged(
+ dataState: Int,
+ networkType: Int
+ ) {
+ state =
+ state.copy(dataConnectionState = dataState.toDataConnectionType())
+ trySend(state)
+ }
+
+ override fun onDataActivity(direction: Int) {
+ state = state.copy(dataActivityDirection = direction)
+ trySend(state)
+ }
+
+ override fun onCarrierNetworkChange(active: Boolean) {
+ state = state.copy(carrierNetworkChangeActive = active)
+ trySend(state)
+ }
+
+ override fun onDisplayInfoChanged(
+ telephonyDisplayInfo: TelephonyDisplayInfo
+ ) {
+
+ val networkType =
+ if (telephonyDisplayInfo.networkType == NETWORK_TYPE_UNKNOWN) {
+ UnknownNetworkType
+ } else if (
+ telephonyDisplayInfo.overrideNetworkType ==
+ OVERRIDE_NETWORK_TYPE_NONE
+ ) {
+ DefaultNetworkType(
+ telephonyDisplayInfo.networkType,
+ mobileMappingsProxy.toIconKey(
+ telephonyDisplayInfo.networkType
+ )
+ )
+ } else {
+ OverrideNetworkType(
+ telephonyDisplayInfo.overrideNetworkType,
+ mobileMappingsProxy.toIconKeyOverride(
+ telephonyDisplayInfo.overrideNetworkType
+ )
+ )
+ }
+ state = state.copy(resolvedNetworkType = networkType)
+ trySend(state)
+ }
+ }
+ telephonyManager.registerTelephonyCallback(bgDispatcher.asExecutor(), callback)
+ awaitClose { telephonyManager.unregisterTelephonyCallback(callback) }
+ }
+ .onEach { telephonyCallbackEvent.tryEmit(Unit) }
+ .logOutputChange(logger, "MobileSubscriptionModel")
+ .stateIn(scope, SharingStarted.WhileSubscribed(), state)
+ }
+
+ /** Produces whenever the mobile data setting changes for this subId */
+ private val localMobileDataSettingChangedEvent: Flow<Unit> = conflatedCallbackFlow {
+ val observer =
+ object : ContentObserver(null) {
+ override fun onChange(selfChange: Boolean) {
+ trySend(Unit)
+ }
+ }
+
+ globalSettings.registerContentObserver(
+ globalSettings.getUriFor("${Global.MOBILE_DATA}$subId"),
+ /* notifyForDescendants */ true,
+ observer
+ )
+
+ awaitClose { context.contentResolver.unregisterContentObserver(observer) }
+ }
+
+ /**
+ * There are a few cases where we will need to poll [TelephonyManager] so we can update some
+ * internal state where callbacks aren't provided. Any of those events should be merged into
+ * this flow, which can be used to trigger the polling.
+ */
+ private val telephonyPollingEvent: Flow<Unit> =
+ merge(
+ telephonyCallbackEvent,
+ localMobileDataSettingChangedEvent,
+ globalMobileDataSettingChangedEvent,
+ )
+
+ override val dataEnabled: StateFlow<Boolean> =
+ telephonyPollingEvent
+ .mapLatest { dataConnectionAllowed() }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), dataConnectionAllowed())
+
+ private fun dataConnectionAllowed(): Boolean = telephonyManager.isDataConnectionAllowed
+
+ override val isDefaultDataSubscription: StateFlow<Boolean> =
+ defaultDataSubId
+ .mapLatest { it == subId }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), defaultDataSubId.value == subId)
+
+ class Factory
+ @Inject
+ constructor(
+ private val context: Context,
+ private val telephonyManager: TelephonyManager,
+ private val logger: ConnectivityPipelineLogger,
+ private val globalSettings: GlobalSettings,
+ private val mobileMappingsProxy: MobileMappingsProxy,
+ @Background private val bgDispatcher: CoroutineDispatcher,
+ @Application private val scope: CoroutineScope,
+ ) {
+ fun build(
+ subId: Int,
+ defaultDataSubId: StateFlow<Int>,
+ globalMobileDataSettingChangedEvent: Flow<Unit>,
+ ): MobileConnectionRepository {
+ return MobileConnectionRepositoryImpl(
+ context,
+ subId,
+ telephonyManager.createForSubscriptionId(subId),
+ globalSettings,
+ defaultDataSubId,
+ globalMobileDataSettingChangedEvent,
+ mobileMappingsProxy,
+ bgDispatcher,
+ logger,
+ scope,
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
new file mode 100644
index 000000000000..f27a9c9cca98
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
@@ -0,0 +1,281 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.content.IntentFilter
+import android.database.ContentObserver
+import android.net.ConnectivityManager
+import android.net.ConnectivityManager.NetworkCallback
+import android.net.Network
+import android.net.NetworkCapabilities
+import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
+import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
+import android.provider.Settings.Global.MOBILE_DATA
+import android.telephony.CarrierConfigManager
+import android.telephony.SubscriptionInfo
+import android.telephony.SubscriptionManager
+import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
+import android.telephony.TelephonyCallback
+import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener
+import android.telephony.TelephonyManager
+import androidx.annotation.VisibleForTesting
+import com.android.internal.telephony.PhoneConstants
+import com.android.settingslib.SignalIcon.MobileIconGroup
+import com.android.settingslib.mobile.MobileMappings
+import com.android.settingslib.mobile.MobileMappings.Config
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.util.settings.GlobalSettings
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.asExecutor
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.withContext
+
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class MobileConnectionsRepositoryImpl
+@Inject
+constructor(
+ private val connectivityManager: ConnectivityManager,
+ private val subscriptionManager: SubscriptionManager,
+ private val telephonyManager: TelephonyManager,
+ private val logger: ConnectivityPipelineLogger,
+ mobileMappingsProxy: MobileMappingsProxy,
+ broadcastDispatcher: BroadcastDispatcher,
+ private val globalSettings: GlobalSettings,
+ private val context: Context,
+ @Background private val bgDispatcher: CoroutineDispatcher,
+ @Application private val scope: CoroutineScope,
+ private val mobileConnectionRepositoryFactory: MobileConnectionRepositoryImpl.Factory
+) : MobileConnectionsRepository {
+ private var subIdRepositoryCache: MutableMap<Int, MobileConnectionRepository> = mutableMapOf()
+
+ /**
+ * State flow that emits the set of mobile data subscriptions, each represented by its own
+ * [SubscriptionInfo]. We probably only need the [SubscriptionInfo.getSubscriptionId] of each
+ * info object, but for now we keep track of the infos themselves.
+ */
+ override val subscriptions: StateFlow<List<SubscriptionModel>> =
+ conflatedCallbackFlow {
+ val callback =
+ object : SubscriptionManager.OnSubscriptionsChangedListener() {
+ override fun onSubscriptionsChanged() {
+ trySend(Unit)
+ }
+ }
+
+ subscriptionManager.addOnSubscriptionsChangedListener(
+ bgDispatcher.asExecutor(),
+ callback,
+ )
+
+ awaitClose { subscriptionManager.removeOnSubscriptionsChangedListener(callback) }
+ }
+ .mapLatest { fetchSubscriptionsList().map { it.toSubscriptionModel() } }
+ .onEach { infos -> dropUnusedReposFromCache(infos) }
+ .stateIn(scope, started = SharingStarted.WhileSubscribed(), listOf())
+
+ /** StateFlow that keeps track of the current active mobile data subscription */
+ override val activeMobileDataSubscriptionId: StateFlow<Int> =
+ conflatedCallbackFlow {
+ val callback =
+ object : TelephonyCallback(), ActiveDataSubscriptionIdListener {
+ override fun onActiveDataSubscriptionIdChanged(subId: Int) {
+ trySend(subId)
+ }
+ }
+
+ telephonyManager.registerTelephonyCallback(bgDispatcher.asExecutor(), callback)
+ awaitClose { telephonyManager.unregisterTelephonyCallback(callback) }
+ }
+ .stateIn(scope, started = SharingStarted.WhileSubscribed(), INVALID_SUBSCRIPTION_ID)
+
+ private val defaultDataSubIdChangeEvent: MutableSharedFlow<Unit> =
+ MutableSharedFlow(extraBufferCapacity = 1)
+
+ override val defaultDataSubId: StateFlow<Int> =
+ broadcastDispatcher
+ .broadcastFlow(
+ IntentFilter(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)
+ ) { intent, _ ->
+ intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY, INVALID_SUBSCRIPTION_ID)
+ }
+ .distinctUntilChanged()
+ .onEach { defaultDataSubIdChangeEvent.tryEmit(Unit) }
+ .stateIn(
+ scope,
+ SharingStarted.WhileSubscribed(),
+ SubscriptionManager.getDefaultDataSubscriptionId()
+ )
+
+ private val carrierConfigChangedEvent =
+ broadcastDispatcher.broadcastFlow(
+ IntentFilter(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)
+ )
+
+ /**
+ * [Config] is an object that tracks relevant configuration flags for a given subscription ID.
+ * In the case of [MobileMappings], it's hard-coded to check the default data subscription's
+ * config, so this will apply to every icon that we care about.
+ *
+ * Relevant bits in the config are things like
+ * [CarrierConfigManager.KEY_SHOW_4G_FOR_LTE_DATA_ICON_BOOL]
+ *
+ * This flow will produce whenever the default data subscription or the carrier config changes.
+ */
+ private val defaultDataSubRatConfig: StateFlow<Config> =
+ merge(defaultDataSubIdChangeEvent, carrierConfigChangedEvent)
+ .mapLatest { Config.readConfig(context) }
+ .stateIn(
+ scope,
+ SharingStarted.WhileSubscribed(),
+ initialValue = Config.readConfig(context)
+ )
+
+ override val defaultMobileIconMapping: Flow<Map<String, MobileIconGroup>> =
+ defaultDataSubRatConfig.map { mobileMappingsProxy.mapIconSets(it) }
+
+ override val defaultMobileIconGroup: Flow<MobileIconGroup> =
+ defaultDataSubRatConfig.map { mobileMappingsProxy.getDefaultIcons(it) }
+
+ override fun getRepoForSubId(subId: Int): MobileConnectionRepository {
+ if (!isValidSubId(subId)) {
+ throw IllegalArgumentException(
+ "subscriptionId $subId is not in the list of valid subscriptions"
+ )
+ }
+
+ return subIdRepositoryCache[subId]
+ ?: createRepositoryForSubId(subId).also { subIdRepositoryCache[subId] = it }
+ }
+
+ /**
+ * In single-SIM devices, the [MOBILE_DATA] setting is phone-wide. For multi-SIM, the individual
+ * connection repositories also observe the URI for [MOBILE_DATA] + subId.
+ */
+ override val globalMobileDataSettingChangedEvent: Flow<Unit> = conflatedCallbackFlow {
+ val observer =
+ object : ContentObserver(null) {
+ override fun onChange(selfChange: Boolean) {
+ trySend(Unit)
+ }
+ }
+
+ globalSettings.registerContentObserver(
+ globalSettings.getUriFor(MOBILE_DATA),
+ true,
+ observer
+ )
+
+ awaitClose { context.contentResolver.unregisterContentObserver(observer) }
+ }
+
+ @SuppressLint("MissingPermission")
+ override val defaultMobileNetworkConnectivity: StateFlow<MobileConnectivityModel> =
+ conflatedCallbackFlow {
+ val callback =
+ object : NetworkCallback(FLAG_INCLUDE_LOCATION_INFO) {
+ override fun onLost(network: Network) {
+ // Send a disconnected model when lost. Maybe should create a sealed
+ // type or null here?
+ trySend(MobileConnectivityModel())
+ }
+
+ override fun onCapabilitiesChanged(
+ network: Network,
+ caps: NetworkCapabilities
+ ) {
+ trySend(
+ MobileConnectivityModel(
+ isConnected = caps.hasTransport(TRANSPORT_CELLULAR),
+ isValidated = caps.hasCapability(NET_CAPABILITY_VALIDATED),
+ )
+ )
+ }
+ }
+
+ connectivityManager.registerDefaultNetworkCallback(callback)
+
+ awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), MobileConnectivityModel())
+
+ private fun isValidSubId(subId: Int): Boolean {
+ subscriptions.value.forEach {
+ if (it.subscriptionId == subId) {
+ return true
+ }
+ }
+
+ return false
+ }
+
+ @VisibleForTesting fun getSubIdRepoCache() = subIdRepositoryCache
+
+ private fun createRepositoryForSubId(subId: Int): MobileConnectionRepository {
+ return mobileConnectionRepositoryFactory.build(
+ subId,
+ defaultDataSubId,
+ globalMobileDataSettingChangedEvent,
+ )
+ }
+
+ private fun dropUnusedReposFromCache(newInfos: List<SubscriptionModel>) {
+ // Remove any connection repository from the cache that isn't in the new set of IDs. They
+ // will get garbage collected once their subscribers go away
+ val currentValidSubscriptionIds = newInfos.map { it.subscriptionId }
+
+ subIdRepositoryCache =
+ subIdRepositoryCache
+ .filter { currentValidSubscriptionIds.contains(it.key) }
+ .toMutableMap()
+ }
+
+ private suspend fun fetchSubscriptionsList(): List<SubscriptionInfo> =
+ withContext(bgDispatcher) { subscriptionManager.completeActiveSubscriptionInfoList }
+
+ private fun SubscriptionInfo.toSubscriptionModel(): SubscriptionModel =
+ SubscriptionModel(
+ subscriptionId = subscriptionId,
+ isOpportunistic = isOpportunistic,
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
index 0da84f0bec9c..8e1197ca7c5c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
@@ -20,10 +20,7 @@ import android.telephony.CarrierConfigManager
import com.android.settingslib.SignalIcon.MobileIconGroup
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState.Connected
-import com.android.systemui.statusbar.pipeline.mobile.data.model.DefaultNetworkType
-import com.android.systemui.statusbar.pipeline.mobile.data.model.OverrideNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
-import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
import com.android.systemui.util.CarrierConfigTracker
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -70,10 +67,9 @@ class MobileIconInteractorImpl(
defaultMobileIconMapping: StateFlow<Map<String, MobileIconGroup>>,
defaultMobileIconGroup: StateFlow<MobileIconGroup>,
override val isDefaultConnectionFailed: StateFlow<Boolean>,
- mobileMappingsProxy: MobileMappingsProxy,
connectionRepository: MobileConnectionRepository,
) : MobileIconInteractor {
- private val mobileStatusInfo = connectionRepository.subscriptionModelFlow
+ private val connectionInfo = connectionRepository.connectionInfo
override val isDataEnabled: StateFlow<Boolean> = connectionRepository.dataEnabled
@@ -82,33 +78,27 @@ class MobileIconInteractorImpl(
/** Observable for the current RAT indicator icon ([MobileIconGroup]) */
override val networkTypeIconGroup: StateFlow<MobileIconGroup> =
combine(
- mobileStatusInfo,
+ connectionInfo,
defaultMobileIconMapping,
defaultMobileIconGroup,
) { info, mapping, defaultGroup ->
- val lookupKey =
- when (val resolved = info.resolvedNetworkType) {
- is DefaultNetworkType -> mobileMappingsProxy.toIconKey(resolved.type)
- is OverrideNetworkType ->
- mobileMappingsProxy.toIconKeyOverride(resolved.type)
- }
- mapping[lookupKey] ?: defaultGroup
+ mapping[info.resolvedNetworkType.lookupKey] ?: defaultGroup
}
.stateIn(scope, SharingStarted.WhileSubscribed(), defaultMobileIconGroup.value)
override val isEmergencyOnly: StateFlow<Boolean> =
- mobileStatusInfo
+ connectionInfo
.mapLatest { it.isEmergencyOnly }
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
override val level: StateFlow<Int> =
- mobileStatusInfo
- .mapLatest { mobileModel ->
+ connectionInfo
+ .mapLatest { connection ->
// TODO: incorporate [MobileMappings.Config.alwaysShowCdmaRssi]
- if (mobileModel.isGsm) {
- mobileModel.primaryLevel
+ if (connection.isGsm) {
+ connection.primaryLevel
} else {
- mobileModel.cdmaLevel
+ connection.cdmaLevel
}
}
.stateIn(scope, SharingStarted.WhileSubscribed(), 0)
@@ -120,7 +110,7 @@ class MobileIconInteractorImpl(
override val numberOfLevels: StateFlow<Int> = MutableStateFlow(4)
override val isDataConnected: StateFlow<Boolean> =
- mobileStatusInfo
- .mapLatest { subscriptionModel -> subscriptionModel.dataConnectionState == Connected }
+ connectionInfo
+ .mapLatest { connection -> connection.dataConnectionState == Connected }
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
index a4175c3a6ab1..6f8fb2e2332b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
@@ -17,17 +17,16 @@
package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
import android.telephony.CarrierConfigManager
-import android.telephony.SubscriptionInfo
import android.telephony.SubscriptionManager
import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
import com.android.settingslib.SignalIcon.MobileIconGroup
import com.android.settingslib.mobile.TelephonyIcons
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository
-import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
import com.android.systemui.util.CarrierConfigTracker
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -53,7 +52,7 @@ import kotlinx.coroutines.flow.stateIn
*/
interface MobileIconsInteractor {
/** List of subscriptions, potentially filtered for CBRS */
- val filteredSubscriptions: Flow<List<SubscriptionInfo>>
+ val filteredSubscriptions: Flow<List<SubscriptionModel>>
/** True if the active mobile data subscription has data enabled */
val activeDataConnectionHasDataEnabled: StateFlow<Boolean>
/** The icon mapping from network type to [MobileIconGroup] for the default subscription */
@@ -79,7 +78,6 @@ class MobileIconsInteractorImpl
constructor(
private val mobileConnectionsRepo: MobileConnectionsRepository,
private val carrierConfigTracker: CarrierConfigTracker,
- private val mobileMappingsProxy: MobileMappingsProxy,
userSetupRepo: UserSetupRepository,
@Application private val scope: CoroutineScope,
) : MobileIconsInteractor {
@@ -102,8 +100,8 @@ constructor(
.flatMapLatest { it?.dataEnabled ?: flowOf(false) }
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
- private val unfilteredSubscriptions: Flow<List<SubscriptionInfo>> =
- mobileConnectionsRepo.subscriptionsFlow
+ private val unfilteredSubscriptions: Flow<List<SubscriptionModel>> =
+ mobileConnectionsRepo.subscriptions
/**
* Generally, SystemUI wants to show iconography for each subscription that is listed by
@@ -118,7 +116,7 @@ constructor(
* [CarrierConfigManager.KEY_ALWAYS_SHOW_PRIMARY_SIGNAL_BAR_IN_OPPORTUNISTIC_NETWORK_BOOLEAN],
* and by checking which subscription is opportunistic, or which one is active.
*/
- override val filteredSubscriptions: Flow<List<SubscriptionInfo>> =
+ override val filteredSubscriptions: Flow<List<SubscriptionModel>> =
combine(unfilteredSubscriptions, activeMobileDataSubscriptionId) { unfilteredSubs, activeId
->
// Based on the old logic,
@@ -154,15 +152,19 @@ constructor(
* subscription Id. This mapping is the same for every subscription.
*/
override val defaultMobileIconMapping: StateFlow<Map<String, MobileIconGroup>> =
- mobileConnectionsRepo.defaultDataSubRatConfig
- .mapLatest { mobileMappingsProxy.mapIconSets(it) }
- .stateIn(scope, SharingStarted.WhileSubscribed(), initialValue = mapOf())
+ mobileConnectionsRepo.defaultMobileIconMapping.stateIn(
+ scope,
+ SharingStarted.WhileSubscribed(),
+ initialValue = mapOf()
+ )
/** If there is no mapping in [defaultMobileIconMapping], then use this default icon group */
override val defaultMobileIconGroup: StateFlow<MobileIconGroup> =
- mobileConnectionsRepo.defaultDataSubRatConfig
- .mapLatest { mobileMappingsProxy.getDefaultIcons(it) }
- .stateIn(scope, SharingStarted.WhileSubscribed(), initialValue = TelephonyIcons.G)
+ mobileConnectionsRepo.defaultMobileIconGroup.stateIn(
+ scope,
+ SharingStarted.WhileSubscribed(),
+ initialValue = TelephonyIcons.G
+ )
/**
* We want to show an error state when cellular has actually failed to validate, but not if some
@@ -189,7 +191,6 @@ constructor(
defaultMobileIconMapping,
defaultMobileIconGroup,
isDefaultConnectionFailed,
- mobileMappingsProxy,
mobileConnectionsRepo.getRepoForSubId(subId),
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
index c7e0ce173ece..62fa723dbf04 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.pipeline.mobile.ui
+import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.statusbar.phone.StatusBarIconController
@@ -29,9 +30,10 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.mapLatest
-import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
/**
* This class is intended to provide a context to collect on the
@@ -50,12 +52,12 @@ constructor(
interactor: MobileIconsInteractor,
private val iconController: StatusBarIconController,
private val iconsViewModelFactory: MobileIconsViewModel.Factory,
- @Application scope: CoroutineScope,
+ @Application private val scope: CoroutineScope,
private val statusBarPipelineFlags: StatusBarPipelineFlags,
-) {
+) : CoreStartable {
private val mobileSubIds: Flow<List<Int>> =
- interactor.filteredSubscriptions.mapLatest { infos ->
- infos.map { subscriptionInfo -> subscriptionInfo.subscriptionId }
+ interactor.filteredSubscriptions.mapLatest { subscriptions ->
+ subscriptions.map { subscriptionModel -> subscriptionModel.subscriptionId }
}
/**
@@ -66,18 +68,19 @@ constructor(
* NOTE: this should go away as the view presenter learns more about this data pipeline
*/
private val mobileSubIdsState: StateFlow<List<Int>> =
- mobileSubIds
- .onEach {
- // Only notify the icon controller if we want to *render* the new icons.
- // Note that this flow may still run if
- // [statusBarPipelineFlags.runNewMobileIconsBackend] is true because we may want to
- // get the logging data without rendering.
- if (statusBarPipelineFlags.useNewMobileIcons()) {
- // Notify the icon controller here so that it knows to add icons
- iconController.setNewMobileIconSubIds(it)
- }
+ mobileSubIds.stateIn(scope, SharingStarted.WhileSubscribed(), listOf())
+
+ override fun start() {
+ // Only notify the icon controller if we want to *render* the new icons.
+ // Note that this flow may still run if
+ // [statusBarPipelineFlags.runNewMobileIconsBackend] is true because we may want to
+ // get the logging data without rendering.
+ if (statusBarPipelineFlags.useNewMobileIcons()) {
+ scope.launch {
+ mobileSubIds.collectLatest { iconController.setNewMobileIconSubIds(it) }
}
- .stateIn(scope, SharingStarted.WhileSubscribed(), listOf())
+ }
+ }
/**
* Create a MobileIconsViewModel for a given [IconManager], and bind it to to the manager's
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt
index ec4fa9ca8128..0ab7bcd96844 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt
@@ -32,6 +32,8 @@ class ModernStatusBarMobileView(
attrs: AttributeSet?,
) : BaseStatusBarFrameLayout(context, attrs) {
+ var subId: Int = -1
+
private lateinit var slot: String
override fun getSlot() = slot
@@ -76,6 +78,7 @@ class ModernStatusBarMobileView(
as ModernStatusBarMobileView)
.also {
it.slot = slot
+ it.subId = viewModel.subscriptionId
MobileIconBinder.bind(it, viewModel)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
index 24c1db995d50..2349cb7c5d80 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
@@ -23,7 +23,7 @@ import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMob
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import javax.inject.Inject
import kotlinx.coroutines.InternalCoroutinesApi
-import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
/**
* View model for describing the system's current mobile cellular connections. The result is a list
@@ -33,7 +33,7 @@ import kotlinx.coroutines.flow.Flow
class MobileIconsViewModel
@Inject
constructor(
- val subscriptionIdsFlow: Flow<List<Int>>,
+ val subscriptionIdsFlow: StateFlow<List<Int>>,
private val interactor: MobileIconsInteractor,
private val logger: ConnectivityPipelineLogger,
) {
@@ -51,7 +51,7 @@ constructor(
private val interactor: MobileIconsInteractor,
private val logger: ConnectivityPipelineLogger,
) {
- fun create(subscriptionIdsFlow: Flow<List<Int>>): MobileIconsViewModel {
+ fun create(subscriptionIdsFlow: StateFlow<List<Int>>): MobileIconsViewModel {
return MobileIconsViewModel(
subscriptionIdsFlow,
interactor,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
index a6635361cb3c..0c9c1cc51ca4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
@@ -43,6 +43,7 @@ import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.SB_LOGGING_TAG
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logInputChange
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.ACTIVITY_PREFIX
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiActivityModel
import java.util.concurrent.Executor
import javax.inject.Inject
@@ -109,7 +110,12 @@ class WifiRepositoryImpl @Inject constructor(
merge(wifiNetworkChangeEvents, wifiStateChangeEvents)
.mapLatest { wifiManager.isWifiEnabled }
.distinctUntilChanged()
- .logInputChange(logger, "enabled")
+ .logDiffsForTable(
+ wifiTableLogBuffer,
+ columnPrefix = "",
+ columnName = "isWifiEnabled",
+ initialValue = wifiManager.isWifiEnabled,
+ )
.stateIn(
scope = scope,
started = SharingStarted.WhileSubscribed(),
@@ -143,7 +149,12 @@ class WifiRepositoryImpl @Inject constructor(
awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
}
.distinctUntilChanged()
- .logInputChange(logger, "isWifiDefault")
+ .logDiffsForTable(
+ wifiTableLogBuffer,
+ columnPrefix = "",
+ columnName = "isWifiDefault",
+ initialValue = false,
+ )
.stateIn(
scope,
started = SharingStarted.WhileSubscribed(),
@@ -233,6 +244,11 @@ class WifiRepositoryImpl @Inject constructor(
awaitClose { wifiManager.unregisterTrafficStateCallback(callback) }
}
}
+ .logDiffsForTable(
+ wifiTableLogBuffer,
+ columnPrefix = ACTIVITY_PREFIX,
+ initialValue = ACTIVITY_DEFAULT,
+ )
.stateIn(
scope,
started = SharingStarted.WhileSubscribed(),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiActivityModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiActivityModel.kt
index 574610605b4e..a4ca41c22ad1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiActivityModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiActivityModel.kt
@@ -16,10 +16,32 @@
package com.android.systemui.statusbar.pipeline.wifi.shared.model
+import com.android.systemui.log.table.Diffable
+import com.android.systemui.log.table.TableRowLogger
+
/** Provides information on the current wifi activity. */
data class WifiActivityModel(
/** True if the wifi has activity in (download). */
val hasActivityIn: Boolean,
/** True if the wifi has activity out (upload). */
val hasActivityOut: Boolean,
-)
+) : Diffable<WifiActivityModel> {
+
+ override fun logDiffs(prevVal: WifiActivityModel, row: TableRowLogger) {
+ if (prevVal.hasActivityIn != hasActivityIn) {
+ row.logChange(COL_ACTIVITY_IN, hasActivityIn)
+ }
+ if (prevVal.hasActivityOut != hasActivityOut) {
+ row.logChange(COL_ACTIVITY_OUT, hasActivityOut)
+ }
+ }
+
+ override fun logFull(row: TableRowLogger) {
+ row.logChange(COL_ACTIVITY_IN, hasActivityIn)
+ row.logChange(COL_ACTIVITY_OUT, hasActivityOut)
+ }
+}
+
+const val ACTIVITY_PREFIX = "wifiActivity"
+private const val COL_ACTIVITY_IN = "in"
+private const val COL_ACTIVITY_OUT = "out"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/WifiUiAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/WifiUiAdapter.kt
index b816364ed4cf..52237605caf9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/WifiUiAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/WifiUiAdapter.kt
@@ -24,6 +24,7 @@ import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.statusbar.phone.StatusBarIconController
import com.android.systemui.statusbar.phone.StatusBarLocation
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel
import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel
import javax.inject.Inject
@@ -73,7 +74,9 @@ constructor(
// Note that this flow may still run if
// [statusBarPipelineFlags.runNewWifiIconBackend] is true because we may
// want to get the logging data without rendering.
- if (wifiIcon != null && statusBarPipelineFlags.useNewWifiIcon()) {
+ if (
+ wifiIcon is WifiIcon.Visible && statusBarPipelineFlags.useNewWifiIcon()
+ ) {
iconController.setNewWifiIcon()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
index 345f8cb75660..f5b5950d33a8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
@@ -30,6 +30,7 @@ import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON
+import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel
import kotlinx.coroutines.InternalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
@@ -92,8 +93,10 @@ object WifiViewBinder {
launch {
viewModel.wifiIcon.collect { wifiIcon ->
- view.isVisible = wifiIcon != null
- wifiIcon?.let { IconViewBinder.bind(wifiIcon, iconView) }
+ view.isVisible = wifiIcon is WifiIcon.Visible
+ if (wifiIcon is WifiIcon.Visible) {
+ IconViewBinder.bind(wifiIcon.icon, iconView)
+ }
}
}
@@ -135,7 +138,7 @@ object WifiViewBinder {
return object : Binding {
override fun getShouldIconBeVisible(): Boolean {
- return viewModel.wifiIcon.value != null
+ return viewModel.wifiIcon.value is WifiIcon.Visible
}
override fun onVisibilityStateChanged(@StatusBarIconView.VisibleState state: Int) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/model/WifiIcon.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/model/WifiIcon.kt
new file mode 100644
index 000000000000..e491d2bbf0d6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/model/WifiIcon.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.wifi.ui.model
+
+import android.annotation.DrawableRes
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.log.table.Diffable
+import com.android.systemui.log.table.TableRowLogger
+
+/** Represents the various states of the wifi icon. */
+sealed interface WifiIcon : Diffable<WifiIcon> {
+ /** Represents a wifi icon that should be hidden (not visible). */
+ object Hidden : WifiIcon {
+ override fun toString() = "hidden"
+ }
+
+ /**
+ * Represents a visible wifi icon that uses [res] as its image and [contentDescription] as its
+ * description.
+ */
+ class Visible(
+ @DrawableRes res: Int,
+ val contentDescription: ContentDescription.Loaded,
+ ) : WifiIcon {
+ val icon = Icon.Resource(res, contentDescription)
+
+ override fun toString() = contentDescription.description.toString()
+ }
+
+ override fun logDiffs(prevVal: WifiIcon, row: TableRowLogger) {
+ if (prevVal.toString() != toString()) {
+ row.logChange(COL_ICON, toString())
+ }
+ }
+
+ override fun logFull(row: TableRowLogger) {
+ row.logChange(COL_ICON, toString())
+ }
+}
+
+private const val COL_ICON = "wifiIcon"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/HomeWifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/HomeWifiViewModel.kt
index 95ab251422b2..a29c9b94e6b8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/HomeWifiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/HomeWifiViewModel.kt
@@ -17,8 +17,8 @@
package com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel
import android.graphics.Color
-import com.android.systemui.common.shared.model.Icon
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
@@ -28,7 +28,7 @@ import kotlinx.coroutines.flow.StateFlow
*/
class HomeWifiViewModel(
statusBarPipelineFlags: StatusBarPipelineFlags,
- wifiIcon: StateFlow<Icon.Resource?>,
+ wifiIcon: StateFlow<WifiIcon>,
isActivityInViewVisible: Flow<Boolean>,
isActivityOutViewVisible: Flow<Boolean>,
isActivityContainerVisible: Flow<Boolean>,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/KeyguardWifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/KeyguardWifiViewModel.kt
index 86535d63f84f..1e190fb898ff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/KeyguardWifiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/KeyguardWifiViewModel.kt
@@ -17,15 +17,15 @@
package com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel
import android.graphics.Color
-import com.android.systemui.common.shared.model.Icon
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
/** A view model for the wifi icon shown on keyguard (lockscreen). */
class KeyguardWifiViewModel(
statusBarPipelineFlags: StatusBarPipelineFlags,
- wifiIcon: StateFlow<Icon.Resource?>,
+ wifiIcon: StateFlow<WifiIcon>,
isActivityInViewVisible: Flow<Boolean>,
isActivityOutViewVisible: Flow<Boolean>,
isActivityContainerVisible: Flow<Boolean>,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt
index 7cbdf5dbdf2d..e35a8fef4528 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt
@@ -17,8 +17,8 @@
package com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel
import android.graphics.Color
-import com.android.systemui.common.shared.model.Icon
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.flowOf
@@ -33,8 +33,8 @@ abstract class LocationBasedWifiViewModel(
statusBarPipelineFlags: StatusBarPipelineFlags,
debugTint: Int,
- /** The wifi icon that should be displayed. Null if we shouldn't display any icon. */
- val wifiIcon: StateFlow<Icon.Resource?>,
+ /** The wifi icon that should be displayed. */
+ val wifiIcon: StateFlow<WifiIcon>,
/** True if the activity in view should be visible. */
val isActivityInViewVisible: Flow<Boolean>,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/QsWifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/QsWifiViewModel.kt
index fd54c5f5062e..18e62b284cb9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/QsWifiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/QsWifiViewModel.kt
@@ -17,15 +17,15 @@
package com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel
import android.graphics.Color
-import com.android.systemui.common.shared.model.Icon
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
/** A view model for the wifi icon shown in quick settings (when the shade is pulled down). */
class QsWifiViewModel(
statusBarPipelineFlags: StatusBarPipelineFlags,
- wifiIcon: StateFlow<Icon.Resource?>,
+ wifiIcon: StateFlow<WifiIcon>,
isActivityInViewVisible: Flow<Boolean>,
isActivityOutViewVisible: Flow<Boolean>,
isActivityContainerVisible: Flow<Boolean>,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
index 0782bbb774eb..ec7ba653cac7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
@@ -17,16 +17,18 @@
package com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel
import android.content.Context
-import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.annotation.VisibleForTesting
import com.android.settingslib.AccessibilityContentDescriptions.WIFI_CONNECTION_STRENGTH
import com.android.settingslib.AccessibilityContentDescriptions.WIFI_NO_CONNECTION
import com.android.systemui.R
import com.android.systemui.common.shared.model.ContentDescription
-import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.statusbar.pipeline.dagger.WifiTableLog
+import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_FULL_ICONS
import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_INTERNET_ICONS
import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_NETWORK
@@ -71,50 +73,39 @@ constructor(
connectivityConstants: ConnectivityConstants,
private val context: Context,
logger: ConnectivityPipelineLogger,
+ @WifiTableLog wifiTableLogBuffer: TableLogBuffer,
interactor: WifiInteractor,
@Application private val scope: CoroutineScope,
statusBarPipelineFlags: StatusBarPipelineFlags,
wifiConstants: WifiConstants,
) {
- /**
- * Returns the drawable resource ID to use for the wifi icon based on the given network.
- * Null if we can't compute the icon.
- */
- @DrawableRes
- private fun WifiNetworkModel.iconResId(): Int? {
+ /** Returns the icon to use based on the given network. */
+ private fun WifiNetworkModel.icon(): WifiIcon {
return when (this) {
- is WifiNetworkModel.CarrierMerged -> null
- is WifiNetworkModel.Inactive -> WIFI_NO_NETWORK
- is WifiNetworkModel.Active ->
- when {
- this.level == null -> null
- this.isValidated -> WIFI_FULL_ICONS[this.level]
- else -> WIFI_NO_INTERNET_ICONS[this.level]
- }
- }
- }
-
- /**
- * Returns the content description for the wifi icon based on the given network.
- * Null if we can't compute the content description.
- */
- private fun WifiNetworkModel.contentDescription(): ContentDescription? {
- return when (this) {
- is WifiNetworkModel.CarrierMerged -> null
- is WifiNetworkModel.Inactive ->
+ is WifiNetworkModel.CarrierMerged -> WifiIcon.Hidden
+ is WifiNetworkModel.Inactive -> WifiIcon.Visible(
+ res = WIFI_NO_NETWORK,
ContentDescription.Loaded(
"${context.getString(WIFI_NO_CONNECTION)},${context.getString(NO_INTERNET)}"
)
+ )
is WifiNetworkModel.Active ->
when (this.level) {
- null -> null
+ null -> WifiIcon.Hidden
else -> {
val levelDesc = context.getString(WIFI_CONNECTION_STRENGTH[this.level])
when {
- this.isValidated -> ContentDescription.Loaded(levelDesc)
+ this.isValidated ->
+ WifiIcon.Visible(
+ WIFI_FULL_ICONS[this.level],
+ ContentDescription.Loaded(levelDesc)
+ )
else ->
- ContentDescription.Loaded(
- "$levelDesc,${context.getString(NO_INTERNET)}"
+ WifiIcon.Visible(
+ WIFI_NO_INTERNET_ICONS[this.level],
+ ContentDescription.Loaded(
+ "$levelDesc,${context.getString(NO_INTERNET)}"
+ )
)
}
}
@@ -122,8 +113,8 @@ constructor(
}
}
- /** The wifi icon that should be displayed. Null if we shouldn't display any icon. */
- private val wifiIcon: StateFlow<Icon.Resource?> =
+ /** The wifi icon that should be displayed. */
+ private val wifiIcon: StateFlow<WifiIcon> =
combine(
interactor.isEnabled,
interactor.isDefault,
@@ -131,22 +122,29 @@ constructor(
interactor.wifiNetwork,
) { isEnabled, isDefault, isForceHidden, wifiNetwork ->
if (!isEnabled || isForceHidden || wifiNetwork is WifiNetworkModel.CarrierMerged) {
- return@combine null
+ return@combine WifiIcon.Hidden
}
- val iconResId = wifiNetwork.iconResId() ?: return@combine null
- val icon = Icon.Resource(iconResId, wifiNetwork.contentDescription())
+ val icon = wifiNetwork.icon()
return@combine when {
isDefault -> icon
wifiConstants.alwaysShowIconIfEnabled -> icon
!connectivityConstants.hasDataCapabilities -> icon
wifiNetwork is WifiNetworkModel.Active && wifiNetwork.isValidated -> icon
- else -> null
+ else -> WifiIcon.Hidden
}
}
- .logOutputChange(logger, "icon") { icon -> icon?.contentDescription.toString() }
- .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = null)
+ .logDiffsForTable(
+ wifiTableLogBuffer,
+ columnPrefix = "",
+ initialValue = WifiIcon.Hidden,
+ )
+ .stateIn(
+ scope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = WifiIcon.Hidden
+ )
/** The wifi activity status. Null if we shouldn't display the activity status. */
private val activity: Flow<WifiActivityModel?> =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt
index 6c66f0bb1e47..341eb3b0425c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt
@@ -68,7 +68,6 @@ public class DeviceControlsControllerImpl @Inject constructor(
internal const val PREFS_CONTROLS_SEEDING_COMPLETED = "SeedingCompleted"
const val PREFS_CONTROLS_FILE = "controls_prefs"
- internal const val PREFS_SETTINGS_DIALOG_ATTEMPTS = "show_settings_attempts"
private const val SEEDING_MAX = 2
}
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
index a9d05d11dc00..ea4020861a09 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
@@ -105,8 +105,9 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora
*
* This method handles inflating and attaching the view, then delegates to [updateView] to
* display the correct information in the view.
+ * @param onViewTimeout a runnable that runs after the view timeout.
*/
- fun displayView(newInfo: T) {
+ fun displayView(newInfo: T, onViewTimeout: Runnable? = null) {
val currentDisplayInfo = displayInfo
// Update our list of active devices by removing it if necessary, then adding back at the
@@ -173,7 +174,10 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora
cancelViewTimeout?.run()
}
cancelViewTimeout = mainExecutor.executeDelayed(
- { removeView(id, REMOVAL_REASON_TIMEOUT) },
+ {
+ removeView(id, REMOVAL_REASON_TIMEOUT)
+ onViewTimeout?.run()
+ },
timeout.toLong()
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
index fb17b693e17e..4d91e35856dc 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
@@ -34,8 +34,8 @@ import com.android.systemui.animation.ViewHierarchyAnimator
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
import com.android.systemui.common.shared.model.Text.Companion.loadText
-import com.android.systemui.common.ui.binder.IconViewBinder
import com.android.systemui.common.ui.binder.TextViewBinder
+import com.android.systemui.common.ui.binder.TintedIconViewBinder
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.FalsingManager
@@ -121,7 +121,7 @@ open class ChipbarCoordinator @Inject constructor(
// ---- Start icon ----
val iconView = currentView.requireViewById<CachingIconView>(R.id.start_icon)
- IconViewBinder.bind(newInfo.startIcon, iconView)
+ TintedIconViewBinder.bind(newInfo.startIcon, iconView)
// ---- Text ----
val textView = currentView.requireViewById<TextView>(R.id.text)
@@ -156,11 +156,14 @@ open class ChipbarCoordinator @Inject constructor(
}
// ---- Overall accessibility ----
- currentView.requireViewById<ViewGroup>(
- R.id.chipbar_inner
- ).contentDescription =
- "${newInfo.startIcon.contentDescription.loadContentDescription(context)} " +
- "${newInfo.text.loadText(context)}"
+ val iconDesc = newInfo.startIcon.icon.contentDescription
+ val loadedIconDesc = if (iconDesc != null) {
+ "${iconDesc.loadContentDescription(context)} "
+ } else {
+ ""
+ }
+ currentView.requireViewById<ViewGroup>(R.id.chipbar_inner).contentDescription =
+ "$loadedIconDesc${newInfo.text.loadText(context)}"
// ---- Haptics ----
newInfo.vibrationEffect?.let {
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt
index b92e0ec0428f..a3eef8032b3b 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt
@@ -18,8 +18,9 @@ package com.android.systemui.temporarydisplay.chipbar
import android.os.VibrationEffect
import android.view.View
-import com.android.systemui.common.shared.model.Icon
+import androidx.annotation.AttrRes
import com.android.systemui.common.shared.model.Text
+import com.android.systemui.common.shared.model.TintedIcon
import com.android.systemui.temporarydisplay.TemporaryViewInfo
/**
@@ -33,7 +34,7 @@ import com.android.systemui.temporarydisplay.TemporaryViewInfo
* @property vibrationEffect an optional vibration effect when the chipbar is displayed
*/
data class ChipbarInfo(
- val startIcon: Icon,
+ val startIcon: TintedIcon,
val text: Text,
val endItem: ChipbarEndItem?,
val vibrationEffect: VibrationEffect? = null,
@@ -41,7 +42,11 @@ data class ChipbarInfo(
override val wakeReason: String,
override val timeoutMs: Int,
override val id: String,
-) : TemporaryViewInfo()
+) : TemporaryViewInfo() {
+ companion object {
+ @AttrRes const val DEFAULT_ICON_TINT_ATTR = android.R.attr.textColorPrimary
+ }
+}
/** The possible items to display at the end of the chipbar. */
sealed class ChipbarEndItem {
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
index ae30ca0e6a61..b2ec27c8ce43 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
@@ -22,6 +22,8 @@ import android.hardware.devicestate.DeviceStateManager
import android.hardware.devicestate.DeviceStateManager.FoldStateListener
import android.hardware.display.DisplayManager
import android.hardware.input.InputManager
+import android.os.Handler
+import android.os.Looper
import android.os.Trace
import android.view.Choreographer
import android.view.Display
@@ -32,14 +34,18 @@ import android.view.SurfaceControlViewHost
import android.view.SurfaceSession
import android.view.WindowManager
import android.view.WindowlessWindowManager
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dagger.qualifiers.UiBackground
import com.android.systemui.statusbar.LightRevealEffect
import com.android.systemui.statusbar.LightRevealScrim
import com.android.systemui.statusbar.LinearLightRevealEffect
+import com.android.systemui.unfold.UnfoldLightRevealOverlayAnimation.AddOverlayReason.FOLD
+import com.android.systemui.unfold.UnfoldLightRevealOverlayAnimation.AddOverlayReason.UNFOLD
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
import com.android.systemui.unfold.updates.RotationChangeProvider
import com.android.systemui.unfold.util.ScaleAwareTransitionProgressProvider.Companion.areAnimationsEnabled
+import com.android.systemui.util.Assert.isMainThread
import com.android.systemui.util.traceSection
import com.android.wm.shell.displayareahelper.DisplayAreaHelper
import java.util.Optional
@@ -59,6 +65,7 @@ constructor(
private val displayAreaHelper: Optional<DisplayAreaHelper>,
@Main private val executor: Executor,
@UiBackground private val backgroundExecutor: Executor,
+ @Background private val bgHandler: Handler,
private val rotationChangeProvider: RotationChangeProvider,
) {
@@ -120,11 +127,11 @@ constructor(
try {
// Add the view only if we are unfolding and this is the first screen on
if (!isFolded && !isUnfoldHandled && contentResolver.areAnimationsEnabled()) {
- addView(onOverlayReady)
+ executeInBackground { addOverlay(onOverlayReady, reason = UNFOLD) }
isUnfoldHandled = true
} else {
// No unfold transition, immediately report that overlay is ready
- ensureOverlayRemoved()
+ executeInBackground { ensureOverlayRemoved() }
onOverlayReady.run()
}
} finally {
@@ -132,13 +139,14 @@ constructor(
}
}
- private fun addView(onOverlayReady: Runnable? = null) {
+ private fun addOverlay(onOverlayReady: Runnable? = null, reason: AddOverlayReason) {
if (!::wwm.isInitialized) {
// Surface overlay is not created yet on the first SysUI launch
onOverlayReady?.run()
return
}
+ ensureInBackground()
ensureOverlayRemoved()
val newRoot = SurfaceControlViewHost(context, context.display!!, wwm)
@@ -146,13 +154,16 @@ constructor(
LightRevealScrim(context, null).apply {
revealEffect = createLightRevealEffect()
isScrimOpaqueChangedListener = Consumer {}
- revealAmount = 0f
+ revealAmount = when (reason) {
+ FOLD -> TRANSPARENT
+ UNFOLD -> BLACK
+ }
}
val params = getLayoutParams()
newRoot.setView(newView, params)
- onOverlayReady?.let { callback ->
+ if (onOverlayReady != null) {
Trace.beginAsyncSection("UnfoldLightRevealOverlayAnimation#relayout", 0)
newRoot.relayout(params) { transaction ->
@@ -170,7 +181,7 @@ constructor(
.setFrameTimelineVsync(vsyncId + 1)
.addTransactionCommittedListener(backgroundExecutor) {
Trace.endAsyncSection("UnfoldLightRevealOverlayAnimation#relayout", 0)
- callback.run()
+ onOverlayReady.run()
}
.apply()
}
@@ -213,13 +224,16 @@ constructor(
}
private fun ensureOverlayRemoved() {
- root?.release()
- root = null
- scrimView = null
+ ensureInBackground()
+ traceSection("ensureOverlayRemoved") {
+ root?.release()
+ root = null
+ scrimView = null
+ }
}
private fun getUnfoldedDisplayInfo(): DisplayInfo =
- displayManager.displays
+ displayManager.getDisplays(DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED)
.asSequence()
.map { DisplayInfo().apply { it.getDisplayInfo(this) } }
.filter { it.type == Display.TYPE_INTERNAL }
@@ -228,17 +242,17 @@ constructor(
private inner class TransitionListener : TransitionProgressListener {
override fun onTransitionProgress(progress: Float) {
- scrimView?.revealAmount = progress
+ executeInBackground { scrimView?.revealAmount = progress }
}
override fun onTransitionFinished() {
- ensureOverlayRemoved()
+ executeInBackground { ensureOverlayRemoved() }
}
override fun onTransitionStarted() {
// Add view for folding case (when unfolding the view is added earlier)
if (scrimView == null) {
- addView()
+ executeInBackground { addOverlay(reason = FOLD) }
}
// Disable input dispatching during transition.
InputManager.getInstance().cancelCurrentTouch()
@@ -250,30 +264,52 @@ constructor(
traceSection("UnfoldLightRevealOverlayAnimation#onRotationChanged") {
if (currentRotation != newRotation) {
currentRotation = newRotation
- scrimView?.revealEffect = createLightRevealEffect()
- root?.relayout(getLayoutParams())
+ executeInBackground {
+ scrimView?.revealEffect = createLightRevealEffect()
+ root?.relayout(getLayoutParams())
+ }
}
}
}
}
+ private fun executeInBackground(f: () -> Unit) {
+ ensureInMainThread()
+ // The UiBackground executor is not used as it doesn't have a prepared looper.
+ bgHandler.post(f)
+ }
+
+ private fun ensureInBackground() {
+ check(Looper.myLooper() == bgHandler.looper) { "Not being executed in the background!" }
+ }
+
+ private fun ensureInMainThread() {
+ isMainThread()
+ }
+
private inner class FoldListener :
FoldStateListener(
context,
Consumer { isFolded ->
if (isFolded) {
- ensureOverlayRemoved()
+ executeInBackground { ensureOverlayRemoved() }
isUnfoldHandled = false
}
this.isFolded = isFolded
}
)
+ private enum class AddOverlayReason { FOLD, UNFOLD }
+
private companion object {
- private const val ROTATION_ANIMATION_OVERLAY_Z_INDEX = Integer.MAX_VALUE
+ const val ROTATION_ANIMATION_OVERLAY_Z_INDEX = Integer.MAX_VALUE
// Put the unfold overlay below the rotation animation screenshot to hide the moment
// when it is rotated but the rotation of the other windows hasn't happen yet
- private const val UNFOLD_OVERLAY_LAYER_Z_INDEX = ROTATION_ANIMATION_OVERLAY_Z_INDEX - 1
+ const val UNFOLD_OVERLAY_LAYER_Z_INDEX = ROTATION_ANIMATION_OVERLAY_Z_INDEX - 1
+
+ // constants for revealAmount.
+ const val TRANSPARENT = 1f
+ const val BLACK = 0f
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/DeviceConfigProxy.java b/packages/SystemUI/src/com/android/systemui/util/DeviceConfigProxy.java
index 0f3eddf2eb7c..bff6132d5e23 100644
--- a/packages/SystemUI/src/com/android/systemui/util/DeviceConfigProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/util/DeviceConfigProxy.java
@@ -50,13 +50,6 @@ public class DeviceConfigProxy {
}
/**
- * Wrapped version of {@link DeviceConfig#enforceReadPermission}.
- */
- public void enforceReadPermission(String namespace) {
- DeviceConfig.enforceReadPermission(namespace);
- }
-
- /**
* Wrapped version of {@link DeviceConfig#getBoolean}.
*/
public boolean getBoolean(
diff --git a/packages/SystemUI/src/com/android/systemui/util/TraceUtils.kt b/packages/SystemUI/src/com/android/systemui/util/TraceUtils.kt
index 5b16ae999aa3..b311318fb111 100644
--- a/packages/SystemUI/src/com/android/systemui/util/TraceUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/TraceUtils.kt
@@ -22,11 +22,22 @@ import android.os.Trace
* Run a block within a [Trace] section.
* Calls [Trace.beginSection] before and [Trace.endSection] after the passed block.
*/
-inline fun <T> traceSection(tag: String, block: () -> T): T {
- Trace.beginSection(tag)
- try {
- return block()
- } finally {
- Trace.endSection()
+inline fun <T> traceSection(tag: String, block: () -> T): T =
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_APP)) {
+ Trace.traceBegin(Trace.TRACE_TAG_APP, tag)
+ try {
+ block()
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_APP)
+ }
+ } else {
+ block()
+ }
+
+class TraceUtils {
+ companion object {
+ inline fun traceRunnable(tag: String, crossinline block: () -> Unit): Runnable {
+ return Runnable { traceSection(tag) { block() } }
+ }
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
index 33c00fbac707..934996633f50 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
@@ -432,6 +432,11 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER |
AudioManager.DEVICE_OUT_BLE_HEADSET)) != 0;
changed |= updateStreamRoutedToBluetoothW(stream, routedToBluetooth);
+ } else if (stream == AudioManager.STREAM_VOICE_CALL) {
+ final boolean routedToBluetooth =
+ (mAudio.getDevicesForStream(AudioManager.STREAM_VOICE_CALL)
+ & AudioManager.DEVICE_OUT_BLE_HEADSET) != 0;
+ changed |= updateStreamRoutedToBluetoothW(stream, routedToBluetooth);
}
return changed;
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 833a6a4193c4..1bc0d08844d7 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -1771,6 +1771,7 @@ public class VolumeDialogImpl implements VolumeDialog,
if (ss.level == row.requestedLevel) {
row.requestedLevel = -1;
}
+ final boolean isVoiceCallStream = row.stream == AudioManager.STREAM_VOICE_CALL;
final boolean isA11yStream = row.stream == STREAM_ACCESSIBILITY;
final boolean isRingStream = row.stream == AudioManager.STREAM_RING;
final boolean isSystemStream = row.stream == AudioManager.STREAM_SYSTEM;
@@ -1815,8 +1816,12 @@ public class VolumeDialogImpl implements VolumeDialog,
} else if (isRingSilent || zenMuted) {
iconRes = row.iconMuteRes;
} else if (ss.routedToBluetooth) {
- iconRes = isStreamMuted(ss) ? R.drawable.ic_volume_media_bt_mute
- : R.drawable.ic_volume_media_bt;
+ if (isVoiceCallStream) {
+ iconRes = R.drawable.ic_volume_bt_sco;
+ } else {
+ iconRes = isStreamMuted(ss) ? R.drawable.ic_volume_media_bt_mute
+ : R.drawable.ic_volume_media_bt;
+ }
} else if (isStreamMuted(ss)) {
iconRes = ss.muted ? R.drawable.ic_volume_media_off : row.iconMuteRes;
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 02738d5ae48b..8ef98f08c60d 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -253,6 +253,12 @@ public final class WMShell implements
splitScreen.onFinishedWakingUp();
}
});
+ mCommandQueue.addCallback(new CommandQueue.Callbacks() {
+ @Override
+ public void goToFullscreenFromSplit() {
+ splitScreen.goToFullscreenFromSplit();
+ }
+ });
}
@VisibleForTesting
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
index 8bbaf3dff1e5..4903d31f89f4 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
@@ -19,6 +19,7 @@ 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.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
@@ -87,6 +88,7 @@ public class KeyguardAbsKeyInputViewControllerTest extends SysuiTestCase {
when(mAbsKeyInputView.isAttachedToWindow()).thenReturn(true);
when(mAbsKeyInputView.requireViewById(R.id.bouncer_message_area))
.thenReturn(mKeyguardMessageArea);
+ when(mAbsKeyInputView.getResources()).thenReturn(getContext().getResources());
mKeyguardAbsKeyInputViewController = new KeyguardAbsKeyInputViewController(mAbsKeyInputView,
mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback,
mKeyguardMessageAreaControllerFactory, mLatencyTracker, mFalsingCollector,
@@ -125,4 +127,22 @@ public class KeyguardAbsKeyInputViewControllerTest extends SysuiTestCase {
verifyZeroInteractions(mKeyguardSecurityCallback);
verifyZeroInteractions(mKeyguardMessageAreaController);
}
+
+ @Test
+ public void onPromptReasonNone_doesNotSetMessage() {
+ mKeyguardAbsKeyInputViewController.showPromptReason(0);
+ verify(mKeyguardMessageAreaController, never()).setMessage(
+ getContext().getResources().getString(R.string.kg_prompt_reason_restart_password),
+ false);
+ }
+
+ @Test
+ public void onPromptReason_setsMessage() {
+ when(mAbsKeyInputView.getPromptReasonStringRes(1)).thenReturn(
+ R.string.kg_prompt_reason_restart_password);
+ mKeyguardAbsKeyInputViewController.showPromptReason(1);
+ verify(mKeyguardMessageAreaController).setMessage(
+ getContext().getResources().getString(R.string.kg_prompt_reason_restart_password),
+ false);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
index afd582a3b822..fa8c8982bccb 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
@@ -87,17 +87,17 @@ private fun faceModel(user: Int) = KeyguardFaceListenModel(
biometricSettingEnabledForUser = false,
bouncerFullyShown = false,
faceAndFpNotAuthenticated = false,
+ faceAuthAllowed = true,
faceDisabled = false,
faceLockedOut = false,
- fpLockedOut = false,
goingToSleep = false,
keyguardAwake = false,
keyguardGoingAway = false,
listeningForFaceAssistant = false,
occludingAppRequestingFaceAuth = false,
primaryUser = false,
- scanningAllowedByStrongAuth = false,
secureCameraLaunched = false,
+ supportsDetect = true,
switchingUser = false,
udfpsBouncerShowing = false,
udfpsFingerDown = false,
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
index ffd95f4041f9..d91279399341 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
@@ -19,6 +19,7 @@ package com.android.keyguard
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.inputmethod.InputMethodManager
+import android.widget.EditText
import androidx.test.filters.SmallTest
import com.android.internal.util.LatencyTracker
import com.android.internal.widget.LockPatternUtils
@@ -29,59 +30,54 @@ import com.android.systemui.util.concurrency.DelayableExecutor
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyString
import org.mockito.Mock
import org.mockito.Mockito
-import org.mockito.Mockito.`when`
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper
class KeyguardPasswordViewControllerTest : SysuiTestCase() {
- @Mock
- private lateinit var keyguardPasswordView: KeyguardPasswordView
- @Mock
- lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
- @Mock
- lateinit var securityMode: KeyguardSecurityModel.SecurityMode
- @Mock
- lateinit var lockPatternUtils: LockPatternUtils
- @Mock
- lateinit var keyguardSecurityCallback: KeyguardSecurityCallback
- @Mock
- lateinit var messageAreaControllerFactory: KeyguardMessageAreaController.Factory
- @Mock
- lateinit var latencyTracker: LatencyTracker
- @Mock
- lateinit var inputMethodManager: InputMethodManager
- @Mock
- lateinit var emergencyButtonController: EmergencyButtonController
- @Mock
- lateinit var mainExecutor: DelayableExecutor
- @Mock
- lateinit var falsingCollector: FalsingCollector
- @Mock
- lateinit var keyguardViewController: KeyguardViewController
- @Mock
- private lateinit var mKeyguardMessageArea: BouncerKeyguardMessageArea
- @Mock
- private lateinit var mKeyguardMessageAreaController:
- KeyguardMessageAreaController<BouncerKeyguardMessageArea>
+ @Mock private lateinit var keyguardPasswordView: KeyguardPasswordView
+ @Mock private lateinit var passwordEntry: EditText
+ @Mock lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+ @Mock lateinit var securityMode: KeyguardSecurityModel.SecurityMode
+ @Mock lateinit var lockPatternUtils: LockPatternUtils
+ @Mock lateinit var keyguardSecurityCallback: KeyguardSecurityCallback
+ @Mock lateinit var messageAreaControllerFactory: KeyguardMessageAreaController.Factory
+ @Mock lateinit var latencyTracker: LatencyTracker
+ @Mock lateinit var inputMethodManager: InputMethodManager
+ @Mock lateinit var emergencyButtonController: EmergencyButtonController
+ @Mock lateinit var mainExecutor: DelayableExecutor
+ @Mock lateinit var falsingCollector: FalsingCollector
+ @Mock lateinit var keyguardViewController: KeyguardViewController
+ @Mock private lateinit var mKeyguardMessageArea: BouncerKeyguardMessageArea
+ @Mock
+ private lateinit var mKeyguardMessageAreaController:
+ KeyguardMessageAreaController<BouncerKeyguardMessageArea>
- private lateinit var keyguardPasswordViewController: KeyguardPasswordViewController
+ private lateinit var keyguardPasswordViewController: KeyguardPasswordViewController
- @Before
- fun setup() {
- MockitoAnnotations.initMocks(this)
- Mockito.`when`(
- keyguardPasswordView
- .requireViewById<BouncerKeyguardMessageArea>(R.id.bouncer_message_area)
- ).thenReturn(mKeyguardMessageArea)
- Mockito.`when`(messageAreaControllerFactory.create(mKeyguardMessageArea))
- .thenReturn(mKeyguardMessageAreaController)
- keyguardPasswordViewController = KeyguardPasswordViewController(
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ Mockito.`when`(
+ keyguardPasswordView.requireViewById<BouncerKeyguardMessageArea>(
+ R.id.bouncer_message_area))
+ .thenReturn(mKeyguardMessageArea)
+ Mockito.`when`(messageAreaControllerFactory.create(mKeyguardMessageArea))
+ .thenReturn(mKeyguardMessageAreaController)
+ Mockito.`when`(keyguardPasswordView.passwordTextViewId).thenReturn(R.id.passwordEntry)
+ Mockito.`when`(keyguardPasswordView.findViewById<EditText>(R.id.passwordEntry))
+ .thenReturn(passwordEntry)
+ `when`(keyguardPasswordView.resources).thenReturn(context.resources)
+ keyguardPasswordViewController =
+ KeyguardPasswordViewController(
keyguardPasswordView,
keyguardUpdateMonitor,
securityMode,
@@ -94,39 +90,48 @@ class KeyguardPasswordViewControllerTest : SysuiTestCase() {
mainExecutor,
mContext.resources,
falsingCollector,
- keyguardViewController
- )
- }
+ keyguardViewController)
+ }
- @Test
- fun testFocusWhenBouncerIsShown() {
- Mockito.`when`(keyguardViewController.isBouncerShowing).thenReturn(true)
- Mockito.`when`(keyguardPasswordView.isShown).thenReturn(true)
- keyguardPasswordViewController.onResume(KeyguardSecurityView.VIEW_REVEALED)
- keyguardPasswordView.post { verify(keyguardPasswordView).requestFocus() }
+ @Test
+ fun testFocusWhenBouncerIsShown() {
+ Mockito.`when`(keyguardViewController.isBouncerShowing).thenReturn(true)
+ Mockito.`when`(keyguardPasswordView.isShown).thenReturn(true)
+ keyguardPasswordViewController.onResume(KeyguardSecurityView.VIEW_REVEALED)
+ keyguardPasswordView.post {
+ verify(keyguardPasswordView).requestFocus()
+ verify(keyguardPasswordView).showKeyboard()
}
+ }
- @Test
- fun testDoNotFocusWhenBouncerIsHidden() {
- Mockito.`when`(keyguardViewController.isBouncerShowing).thenReturn(false)
- Mockito.`when`(keyguardPasswordView.isShown).thenReturn(true)
- keyguardPasswordViewController.onResume(KeyguardSecurityView.VIEW_REVEALED)
- verify(keyguardPasswordView, never()).requestFocus()
- }
+ @Test
+ fun testDoNotFocusWhenBouncerIsHidden() {
+ Mockito.`when`(keyguardViewController.isBouncerShowing).thenReturn(false)
+ Mockito.`when`(keyguardPasswordView.isShown).thenReturn(true)
+ keyguardPasswordViewController.onResume(KeyguardSecurityView.VIEW_REVEALED)
+ verify(keyguardPasswordView, never()).requestFocus()
+ }
- @Test
- fun startAppearAnimation() {
- keyguardPasswordViewController.startAppearAnimation()
- verify(mKeyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_password)
+ @Test
+ fun testHideKeyboardWhenOnPause() {
+ keyguardPasswordViewController.onPause()
+ keyguardPasswordView.post {
+ verify(keyguardPasswordView).clearFocus()
+ verify(keyguardPasswordView).hideKeyboard()
}
+ }
- @Test
- fun startAppearAnimation_withExistingMessage() {
- `when`(mKeyguardMessageAreaController.message).thenReturn("Unlock to continue.")
- keyguardPasswordViewController.startAppearAnimation()
- verify(
- mKeyguardMessageAreaController,
- never()
- ).setMessage(R.string.keyguard_enter_your_password)
- }
+ @Test
+ fun startAppearAnimation() {
+ keyguardPasswordViewController.startAppearAnimation()
+ verify(mKeyguardMessageAreaController)
+ .setMessage(context.resources.getString(R.string.keyguard_enter_your_password), false)
+ }
+
+ @Test
+ fun startAppearAnimation_withExistingMessage() {
+ `when`(mKeyguardMessageAreaController.message).thenReturn("Unlock to continue.")
+ keyguardPasswordViewController.startAppearAnimation()
+ verify(mKeyguardMessageAreaController, never()).setMessage(anyString(), anyBoolean())
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
index b3d1c8f909d8..85dbdb8330a3 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
@@ -30,97 +30,93 @@ import com.android.systemui.statusbar.policy.DevicePostureController
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyString
import org.mockito.Mock
+import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
-import org.mockito.Mockito.never
import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper
class KeyguardPatternViewControllerTest : SysuiTestCase() {
- @Mock
- private lateinit var mKeyguardPatternView: KeyguardPatternView
+ @Mock private lateinit var mKeyguardPatternView: KeyguardPatternView
- @Mock
- private lateinit var mKeyguardUpdateMonitor: KeyguardUpdateMonitor
+ @Mock private lateinit var mKeyguardUpdateMonitor: KeyguardUpdateMonitor
- @Mock
- private lateinit var mSecurityMode: KeyguardSecurityModel.SecurityMode
+ @Mock private lateinit var mSecurityMode: KeyguardSecurityModel.SecurityMode
- @Mock
- private lateinit var mLockPatternUtils: LockPatternUtils
+ @Mock private lateinit var mLockPatternUtils: LockPatternUtils
- @Mock
- private lateinit var mKeyguardSecurityCallback: KeyguardSecurityCallback
+ @Mock private lateinit var mKeyguardSecurityCallback: KeyguardSecurityCallback
- @Mock
- private lateinit var mLatencyTracker: LatencyTracker
- private var mFalsingCollector: FalsingCollector = FalsingCollectorFake()
+ @Mock private lateinit var mLatencyTracker: LatencyTracker
+ private var mFalsingCollector: FalsingCollector = FalsingCollectorFake()
- @Mock
- private lateinit var mEmergencyButtonController: EmergencyButtonController
+ @Mock private lateinit var mEmergencyButtonController: EmergencyButtonController
- @Mock
- private lateinit
- var mKeyguardMessageAreaControllerFactory: KeyguardMessageAreaController.Factory
+ @Mock
+ private lateinit var mKeyguardMessageAreaControllerFactory: KeyguardMessageAreaController.Factory
- @Mock
- private lateinit var mKeyguardMessageArea: BouncerKeyguardMessageArea
+ @Mock private lateinit var mKeyguardMessageArea: BouncerKeyguardMessageArea
- @Mock
- private lateinit var mKeyguardMessageAreaController:
- KeyguardMessageAreaController<BouncerKeyguardMessageArea>
+ @Mock
+ private lateinit var mKeyguardMessageAreaController:
+ KeyguardMessageAreaController<BouncerKeyguardMessageArea>
- @Mock
- private lateinit var mLockPatternView: LockPatternView
+ @Mock private lateinit var mLockPatternView: LockPatternView
- @Mock
- private lateinit var mPostureController: DevicePostureController
+ @Mock private lateinit var mPostureController: DevicePostureController
- private lateinit var mKeyguardPatternViewController: KeyguardPatternViewController
+ private lateinit var mKeyguardPatternViewController: KeyguardPatternViewController
- @Before
- fun setup() {
- MockitoAnnotations.initMocks(this)
- `when`(mKeyguardPatternView.isAttachedToWindow).thenReturn(true)
- `when`(mKeyguardPatternView
- .requireViewById<BouncerKeyguardMessageArea>(R.id.bouncer_message_area))
- .thenReturn(mKeyguardMessageArea)
- `when`(mKeyguardPatternView.findViewById<LockPatternView>(R.id.lockPatternView))
- .thenReturn(mLockPatternView)
- `when`(mKeyguardMessageAreaControllerFactory.create(mKeyguardMessageArea))
- .thenReturn(mKeyguardMessageAreaController)
- mKeyguardPatternViewController = KeyguardPatternViewController(
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ `when`(mKeyguardPatternView.isAttachedToWindow).thenReturn(true)
+ `when`(
+ mKeyguardPatternView.requireViewById<BouncerKeyguardMessageArea>(
+ R.id.bouncer_message_area))
+ .thenReturn(mKeyguardMessageArea)
+ `when`(mKeyguardPatternView.findViewById<LockPatternView>(R.id.lockPatternView))
+ .thenReturn(mLockPatternView)
+ `when`(mKeyguardMessageAreaControllerFactory.create(mKeyguardMessageArea))
+ .thenReturn(mKeyguardMessageAreaController)
+ `when`(mKeyguardPatternView.resources).thenReturn(context.resources)
+ mKeyguardPatternViewController =
+ KeyguardPatternViewController(
mKeyguardPatternView,
- mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback,
- mLatencyTracker, mFalsingCollector, mEmergencyButtonController,
- mKeyguardMessageAreaControllerFactory, mPostureController
- )
- }
-
- @Test
- fun onPause_resetsText() {
- mKeyguardPatternViewController.init()
- mKeyguardPatternViewController.onPause()
- verify(mKeyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_pattern)
- }
-
-
- @Test
- fun startAppearAnimation() {
- mKeyguardPatternViewController.startAppearAnimation()
- verify(mKeyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_pattern)
- }
-
- @Test
- fun startAppearAnimation_withExistingMessage() {
- `when`(mKeyguardMessageAreaController.message).thenReturn("Unlock to continue.")
- mKeyguardPatternViewController.startAppearAnimation()
- verify(
- mKeyguardMessageAreaController,
- never()
- ).setMessage(R.string.keyguard_enter_your_password)
- }
+ mKeyguardUpdateMonitor,
+ mSecurityMode,
+ mLockPatternUtils,
+ mKeyguardSecurityCallback,
+ mLatencyTracker,
+ mFalsingCollector,
+ mEmergencyButtonController,
+ mKeyguardMessageAreaControllerFactory,
+ mPostureController)
+ }
+
+ @Test
+ fun onPause_resetsText() {
+ mKeyguardPatternViewController.init()
+ mKeyguardPatternViewController.onPause()
+ verify(mKeyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_pattern)
+ }
+
+ @Test
+ fun startAppearAnimation() {
+ mKeyguardPatternViewController.startAppearAnimation()
+ verify(mKeyguardMessageAreaController)
+ .setMessage(context.resources.getString(R.string.keyguard_enter_your_pattern), false)
+ }
+
+ @Test
+ fun startAppearAnimation_withExistingMessage() {
+ `when`(mKeyguardMessageAreaController.message).thenReturn("Unlock to continue.")
+ mKeyguardPatternViewController.startAppearAnimation()
+ verify(mKeyguardMessageAreaController, never()).setMessage(anyString(), anyBoolean())
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
index 8bcfe6f2b6f5..cdb7bbb9f823 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
@@ -31,10 +31,13 @@ import com.android.systemui.statusbar.policy.DevicePostureController
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyString
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.any
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
@SmallTest
@@ -79,6 +82,7 @@ class KeyguardPinViewControllerTest : SysuiTestCase() {
keyguardMessageAreaControllerFactory.create(any(KeyguardMessageArea::class.java))
)
.thenReturn(keyguardMessageAreaController)
+ `when`(keyguardPinView.resources).thenReturn(context.resources)
pinViewController =
KeyguardPinViewController(
keyguardPinView,
@@ -98,14 +102,14 @@ class KeyguardPinViewControllerTest : SysuiTestCase() {
@Test
fun startAppearAnimation() {
pinViewController.startAppearAnimation()
- verify(keyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_pin)
+ verify(keyguardMessageAreaController)
+ .setMessage(context.resources.getString(R.string.keyguard_enter_your_pin), false)
}
@Test
fun startAppearAnimation_withExistingMessage() {
Mockito.`when`(keyguardMessageAreaController.message).thenReturn("Unlock to continue.")
pinViewController.startAppearAnimation()
- verify(keyguardMessageAreaController, Mockito.never())
- .setMessage(R.string.keyguard_enter_your_password)
+ verify(keyguardMessageAreaController, Mockito.never()).setMessage(anyString(), anyBoolean())
}
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 4e358ddd49ea..bdd29aa93b2c 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -27,9 +27,11 @@ import static android.telephony.SubscriptionManager.NAME_SOURCE_CARRIER_ID;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
import static com.android.keyguard.FaceAuthApiRequestReason.NOTIFICATION_PANEL_CLICKED;
import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_STATE_CANCELLING_RESTARTING;
import static com.android.keyguard.KeyguardUpdateMonitor.DEFAULT_CANCEL_SIGNAL_TIMEOUT;
+import static com.android.keyguard.KeyguardUpdateMonitor.HAL_POWER_PRESS_TIMEOUT;
import static com.android.keyguard.KeyguardUpdateMonitor.getCurrentUser;
import static com.google.common.truth.Truth.assertThat;
@@ -101,6 +103,8 @@ import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import androidx.annotation.NonNull;
+
import com.android.dx.mockito.inline.extended.ExtendedMockito;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.InstanceId;
@@ -267,21 +271,9 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
// IBiometricsFace@1.0 does not support detection, only authentication.
when(mFaceSensorProperties.isEmpty()).thenReturn(false);
+ when(mFaceSensorProperties.get(anyInt())).thenReturn(
+ createFaceSensorProperties(/* supportsFaceDetection = */ false));
- final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
- componentInfo.add(new ComponentInfoInternal("faceSensor" /* componentId */,
- "vendor/model/revision" /* hardwareVersion */, "1.01" /* firmwareVersion */,
- "00000001" /* serialNumber */, "" /* softwareVersion */));
- componentInfo.add(new ComponentInfoInternal("matchingAlgorithm" /* componentId */,
- "" /* hardwareVersion */, "" /* firmwareVersion */, "" /* serialNumber */,
- "vendor/version/revision" /* softwareVersion */));
-
- when(mFaceSensorProperties.get(anyInt())).thenReturn(new FaceSensorPropertiesInternal(
- 0 /* id */,
- FaceSensorProperties.STRENGTH_STRONG, 1 /* maxTemplatesAllowed */,
- componentInfo, FaceSensorProperties.TYPE_UNKNOWN,
- false /* supportsFaceDetection */, true /* supportsSelfIllumination */,
- false /* resetLockoutRequiresChallenge */));
when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
when(mFingerprintManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
when(mFingerprintManager.getSensorPropertiesInternal()).thenReturn(List.of(
@@ -354,6 +346,28 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
when(mAuthController.areAllFingerprintAuthenticatorsRegistered()).thenReturn(true);
}
+ @NonNull
+ private FaceSensorPropertiesInternal createFaceSensorProperties(boolean supportsFaceDetection) {
+ final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
+ componentInfo.add(new ComponentInfoInternal("faceSensor" /* componentId */,
+ "vendor/model/revision" /* hardwareVersion */, "1.01" /* firmwareVersion */,
+ "00000001" /* serialNumber */, "" /* softwareVersion */));
+ componentInfo.add(new ComponentInfoInternal("matchingAlgorithm" /* componentId */,
+ "" /* hardwareVersion */, "" /* firmwareVersion */, "" /* serialNumber */,
+ "vendor/version/revision" /* softwareVersion */));
+
+
+ return new FaceSensorPropertiesInternal(
+ 0 /* id */,
+ FaceSensorProperties.STRENGTH_STRONG,
+ 1 /* maxTemplatesAllowed */,
+ componentInfo,
+ FaceSensorProperties.TYPE_UNKNOWN,
+ supportsFaceDetection /* supportsFaceDetection */,
+ true /* supportsSelfIllumination */,
+ false /* resetLockoutRequiresChallenge */);
+ }
+
@After
public void tearDown() {
if (mMockitoSession != null) {
@@ -611,7 +625,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
@Test
public void testUnlockingWithFaceAllowed_strongAuthTrackerUnlockingWithBiometricAllowed() {
// GIVEN unlocking with biometric is allowed
- when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
+ strongAuthNotRequired();
// THEN unlocking with face and fp is allowed
Assert.assertTrue(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
@@ -633,7 +647,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
@Test
public void testUnlockingWithFaceAllowed_fingerprintLockout() {
// GIVEN unlocking with biometric is allowed
- when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
+ strongAuthNotRequired();
// WHEN fingerprint is locked out
fingerprintErrorLockedOut();
@@ -656,7 +670,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
@Test
public void testUnlockingWithFpAllowed_fingerprintLockout() {
// GIVEN unlocking with biometric is allowed
- when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
+ strongAuthNotRequired();
// WHEN fingerprint is locked out
fingerprintErrorLockedOut();
@@ -710,10 +724,9 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
}
@Test
- public void skipsAuthentication_whenEncryptedKeyguard() {
- when(mStrongAuthTracker.getStrongAuthForUser(anyInt())).thenReturn(
- STRONG_AUTH_REQUIRED_AFTER_BOOT);
- mKeyguardUpdateMonitor.setKeyguardBypassController(mKeyguardBypassController);
+ public void skipsAuthentication_whenStrongAuthRequired_nonBypass() {
+ lockscreenBypassIsNotAllowed();
+ when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
mTestableLooper.processAllMessages();
@@ -723,15 +736,48 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
}
@Test
- public void requiresAuthentication_whenEncryptedKeyguard_andBypass() {
- testStrongAuthExceptOnBouncer(
- STRONG_AUTH_REQUIRED_AFTER_BOOT);
+ public void faceDetect_whenStrongAuthRequiredAndBypass() {
+ // GIVEN bypass is enabled, face detection is supported and strong auth is required
+ lockscreenBypassIsAllowed();
+ supportsFaceDetection();
+ strongAuthRequiredEncrypted();
+ keyguardIsVisible();
+
+ // WHEN the device wakes up
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
+ mTestableLooper.processAllMessages();
+
+ // FACE detect is triggered, not authenticate
+ verify(mFaceManager).detectFace(any(), any(), anyInt());
+ verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt(),
+ anyBoolean());
+
+ // WHEN bouncer becomes visible
+ setKeyguardBouncerVisibility(true);
+ clearInvocations(mFaceManager);
+
+ // THEN face scanning is not run
+ mKeyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.UDFPS_POINTER_DOWN);
+ verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt(),
+ anyBoolean());
+ verify(mFaceManager, never()).detectFace(any(), any(), anyInt());
}
@Test
- public void requiresAuthentication_whenTimeoutKeyguard_andBypass() {
- testStrongAuthExceptOnBouncer(
- KeyguardUpdateMonitor.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT);
+ public void noFaceDetect_whenStrongAuthRequiredAndBypass_faceDetectionUnsupported() {
+ // GIVEN bypass is enabled, face detection is NOT supported and strong auth is required
+ lockscreenBypassIsAllowed();
+ strongAuthRequiredEncrypted();
+ keyguardIsVisible();
+
+ // WHEN the device wakes up
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
+ mTestableLooper.processAllMessages();
+
+ // FACE detect and authenticate are NOT triggered
+ verify(mFaceManager, never()).detectFace(any(), any(), anyInt());
+ verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt(),
+ anyBoolean());
}
@Test
@@ -764,24 +810,6 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
assertThat(didFaceAuthRun).isFalse();
}
- private void testStrongAuthExceptOnBouncer(int strongAuth) {
- when(mKeyguardBypassController.canBypass()).thenReturn(true);
- mKeyguardUpdateMonitor.setKeyguardBypassController(mKeyguardBypassController);
- when(mStrongAuthTracker.getStrongAuthForUser(anyInt())).thenReturn(strongAuth);
-
- mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
- mTestableLooper.processAllMessages();
- keyguardIsVisible();
- verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean());
-
- // Stop scanning when bouncer becomes visible
- setKeyguardBouncerVisibility(true);
- clearInvocations(mFaceManager);
- mKeyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.UDFPS_POINTER_DOWN);
- verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt(),
- anyBoolean());
- }
-
@Test
public void testTriesToAuthenticate_whenAssistant() {
mKeyguardUpdateMonitor.setKeyguardShowing(true, true);
@@ -792,10 +820,9 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
@Test
public void testTriesToAuthenticate_whenTrustOnAgentKeyguard_ifBypass() {
- mKeyguardUpdateMonitor.setKeyguardBypassController(mKeyguardBypassController);
mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
mTestableLooper.processAllMessages();
- when(mKeyguardBypassController.canBypass()).thenReturn(true);
+ lockscreenBypassIsAllowed();
mKeyguardUpdateMonitor.onTrustChanged(true /* enabled */,
KeyguardUpdateMonitor.getCurrentUser(), 0 /* flags */,
new ArrayList<>());
@@ -815,26 +842,35 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
}
@Test
- public void testIgnoresAuth_whenLockdown() {
+ public void testNoFaceAuth_whenLockDown() {
+ when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
+ userDeviceLockDown();
+
mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
+ keyguardIsVisible();
mTestableLooper.processAllMessages();
- when(mStrongAuthTracker.getStrongAuthForUser(anyInt())).thenReturn(
- KeyguardUpdateMonitor.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
- keyguardIsVisible();
verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt(),
anyBoolean());
+ verify(mFaceManager, never()).detectFace(any(), any(), anyInt());
}
@Test
- public void testTriesToAuthenticate_whenLockout() {
- mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
+ public void testFingerprintPowerPressed_restartsFingerprintListeningStateWithDelay() {
+ mKeyguardUpdateMonitor.mFingerprintAuthenticationCallback
+ .onAuthenticationError(FingerprintManager.BIOMETRIC_ERROR_POWER_PRESSED, "");
+
+ // THEN doesn't authenticate immediately
+ verify(mFingerprintManager, never()).authenticate(any(),
+ any(), any(), any(), anyInt(), anyInt(), anyInt());
+
+ // WHEN all messages (with delays) are processed
+ mTestableLooper.moveTimeForward(HAL_POWER_PRESS_TIMEOUT);
mTestableLooper.processAllMessages();
- when(mStrongAuthTracker.getStrongAuthForUser(anyInt())).thenReturn(
- KeyguardUpdateMonitor.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT);
- keyguardIsVisible();
- verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean());
+ // THEN fingerprint manager attempts to authenticate again
+ verify(mFingerprintManager).authenticate(any(),
+ any(), any(), any(), anyInt(), anyInt(), anyInt());
}
@Test
@@ -977,8 +1013,6 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean());
verify(mFingerprintManager).authenticate(any(), any(), any(), any(), anyInt(), anyInt(),
anyInt());
-// resetFaceManager();
-// resetFingerprintManager();
when(mFingerprintManager.getLockoutModeForUser(eq(FINGERPRINT_SENSOR_ID), eq(newUser)))
.thenReturn(fingerprintLockoutMode);
@@ -1321,7 +1355,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
mStatusBarStateListener.onStateChanged(StatusBarState.SHADE_LOCKED);
setKeyguardBouncerVisibility(false /* isVisible */);
mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
- when(mKeyguardBypassController.canBypass()).thenReturn(true);
+ lockscreenBypassIsAllowed();
keyguardIsVisible();
// WHEN status bar state reports a change to the keyguard that would normally indicate to
@@ -1505,11 +1539,9 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
userNotCurrentlySwitching();
// This disables face auth
- when(mStrongAuthTracker.getStrongAuthForUser(KeyguardUpdateMonitor.getCurrentUser()))
- .thenReturn(STRONG_AUTH_REQUIRED_AFTER_BOOT);
+ when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
mTestableLooper.processAllMessages();
-
assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
}
@@ -1973,6 +2005,109 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
);
}
+ @Test
+ public void testStrongAuthChange_lockDown_stopsFpAndFaceListeningState() {
+ // GIVEN device is listening for face and fingerprint
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
+ mTestableLooper.processAllMessages();
+ keyguardIsVisible();
+
+ verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean());
+ verify(mFingerprintManager).authenticate(any(), any(), any(), any(), anyInt(), anyInt(),
+ anyInt());
+
+ final CancellationSignal faceCancel = spy(mKeyguardUpdateMonitor.mFaceCancelSignal);
+ final CancellationSignal fpCancel = spy(mKeyguardUpdateMonitor.mFingerprintCancelSignal);
+ mKeyguardUpdateMonitor.mFaceCancelSignal = faceCancel;
+ mKeyguardUpdateMonitor.mFingerprintCancelSignal = fpCancel;
+ KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class);
+ mKeyguardUpdateMonitor.registerCallback(callback);
+
+ // WHEN strong auth changes and device is in user lockdown
+ when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
+ userDeviceLockDown();
+ mKeyguardUpdateMonitor.notifyStrongAuthAllowedChanged(getCurrentUser());
+ mTestableLooper.processAllMessages();
+
+ // THEN face and fingerprint listening are cancelled
+ verify(faceCancel).cancel();
+ verify(callback).onBiometricRunningStateChanged(
+ eq(false), eq(BiometricSourceType.FACE));
+ verify(fpCancel).cancel();
+ verify(callback).onBiometricRunningStateChanged(
+ eq(false), eq(BiometricSourceType.FINGERPRINT));
+ }
+
+ @Test
+ public void testNonStrongBiometricAllowedChanged_stopsFaceListeningState() {
+ // GIVEN device is listening for face and fingerprint
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
+ mTestableLooper.processAllMessages();
+ keyguardIsVisible();
+
+ verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean());
+
+ final CancellationSignal faceCancel = spy(mKeyguardUpdateMonitor.mFaceCancelSignal);
+ mKeyguardUpdateMonitor.mFaceCancelSignal = faceCancel;
+ KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class);
+ mKeyguardUpdateMonitor.registerCallback(callback);
+
+ // WHEN non-strong biometric allowed changes
+ when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
+ mKeyguardUpdateMonitor.notifyNonStrongBiometricAllowedChanged(getCurrentUser());
+ mTestableLooper.processAllMessages();
+
+ // THEN face and fingerprint listening are cancelled
+ verify(faceCancel).cancel();
+ verify(callback).onBiometricRunningStateChanged(
+ eq(false), eq(BiometricSourceType.FACE));
+ }
+
+ @Test
+ public void testShouldListenForFace_withLockedDown_returnsFalse()
+ throws RemoteException {
+ keyguardNotGoingAway();
+ bouncerFullyVisibleAndNotGoingToSleep();
+ currentUserIsPrimary();
+ currentUserDoesNotHaveTrust();
+ biometricsNotDisabledThroughDevicePolicyManager();
+ biometricsEnabledForCurrentUser();
+ userNotCurrentlySwitching();
+ supportsFaceDetection();
+ mTestableLooper.processAllMessages();
+
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
+
+ userDeviceLockDown();
+
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
+ }
+
+ private void userDeviceLockDown() {
+ when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
+ when(mStrongAuthTracker.getStrongAuthForUser(mCurrentUserId))
+ .thenReturn(STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
+ }
+
+ private void supportsFaceDetection() {
+ when(mFaceSensorProperties.get(anyInt()))
+ .thenReturn(createFaceSensorProperties(
+ /* supportsFaceDetection = */ true));
+ }
+
+ private void lockscreenBypassIsAllowed() {
+ mockCanBypassLockscreen(true);
+ }
+
+ private void mockCanBypassLockscreen(boolean canBypass) {
+ mKeyguardUpdateMonitor.setKeyguardBypassController(mKeyguardBypassController);
+ when(mKeyguardBypassController.canBypass()).thenReturn(canBypass);
+ }
+
+ private void lockscreenBypassIsNotAllowed() {
+ mockCanBypassLockscreen(false);
+ }
+
private void cleanupKeyguardUpdateMonitor() {
if (mKeyguardUpdateMonitor != null) {
mKeyguardUpdateMonitor.removeCallback(mTestCallback);
@@ -2066,9 +2201,16 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
);
}
+ private void strongAuthRequiredEncrypted() {
+ when(mStrongAuthTracker.getStrongAuthForUser(KeyguardUpdateMonitor.getCurrentUser()))
+ .thenReturn(STRONG_AUTH_REQUIRED_AFTER_BOOT);
+ when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
+ }
+
private void strongAuthNotRequired() {
when(mStrongAuthTracker.getStrongAuthForUser(KeyguardUpdateMonitor.getCurrentUser()))
.thenReturn(0);
+ when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
}
private void currentUserDoesNotHaveTrust() {
@@ -2109,6 +2251,10 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
setKeyguardBouncerVisibility(true);
}
+ private void bouncerNotVisible() {
+ setKeyguardBouncerVisibility(false);
+ }
+
private void setKeyguardBouncerVisibility(boolean isVisible) {
mKeyguardUpdateMonitor.sendPrimaryBouncerChanged(isVisible, isVisible);
mTestableLooper.processAllMessages();
diff --git a/packages/SystemUI/animation/tests/com/android/systemui/animation/InterpolatorsAndroidXTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/InterpolatorsAndroidXTest.kt
index 389eed015820..2c680be97e95 100644
--- a/packages/SystemUI/animation/tests/com/android/systemui/animation/InterpolatorsAndroidXTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/InterpolatorsAndroidXTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.animation
import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
import java.lang.reflect.Modifier
import junit.framework.Assert.assertEquals
import org.junit.Test
@@ -25,7 +26,7 @@ import org.junit.runners.JUnit4
@SmallTest
@RunWith(JUnit4::class)
-class InterpolatorsAndroidXTest {
+class InterpolatorsAndroidXTest : SysuiTestCase() {
@Test
fun testInterpolatorsAndInterpolatorsAndroidXPublicMethodsAreEqual() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
index eb8c823ffe1c..b765ab3c5eac 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
@@ -26,6 +26,7 @@ import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FeatureFlags
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.LightRevealScrim
@@ -77,6 +78,7 @@ class AuthRippleControllerTest : SysuiTestCase() {
@Mock private lateinit var udfpsControllerProvider: Provider<UdfpsController>
@Mock private lateinit var udfpsController: UdfpsController
@Mock private lateinit var statusBarStateController: StatusBarStateController
+ @Mock private lateinit var featureFlags: FeatureFlags
@Mock private lateinit var lightRevealScrim: LightRevealScrim
@Mock private lateinit var fpSensorProp: FingerprintSensorPropertiesInternal
@@ -106,6 +108,7 @@ class AuthRippleControllerTest : SysuiTestCase() {
biometricUnlockController,
udfpsControllerProvider,
statusBarStateController,
+ featureFlags,
rippleView
)
controller.init()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index b267a5c23a49..a94f3427eebe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -110,6 +110,8 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
+import javax.inject.Provider;
+
@SmallTest
@RunWith(AndroidTestingRunner.class)
@RunWithLooper(setAsMainLooper = true)
@@ -261,6 +263,7 @@ public class UdfpsControllerTest extends SysuiTestCase {
initUdfpsController(true /* hasAlternateTouchProvider */);
}
+
private void initUdfpsController(boolean hasAlternateTouchProvider) {
initUdfpsController(mOpticalProps, hasAlternateTouchProvider);
}
@@ -270,8 +273,10 @@ public class UdfpsControllerTest extends SysuiTestCase {
reset(mFingerprintManager);
reset(mScreenLifecycle);
- final Optional<AlternateUdfpsTouchProvider> alternateTouchProvider =
- hasAlternateTouchProvider ? Optional.of(mAlternateTouchProvider) : Optional.empty();
+ final Optional<Provider<AlternateUdfpsTouchProvider>> alternateTouchProvider =
+ hasAlternateTouchProvider ? Optional.of(
+ (Provider<AlternateUdfpsTouchProvider>) () -> mAlternateTouchProvider)
+ : Optional.empty();
mUdfpsController = new UdfpsController(mContext, new FakeExecution(), mLayoutInflater,
mFingerprintManager, mWindowManager, mStatusBarStateController, mFgExecutor,
@@ -1140,7 +1145,7 @@ public class UdfpsControllerTest extends SysuiTestCase {
}
@Test
- public void onTouch_withNewTouchDetection_shouldCallOldFingerprintManagerPath()
+ public void onTouch_withNewTouchDetection_shouldCallNewFingerprintManagerPath()
throws RemoteException {
final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L,
0L);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
index 1d00d6b05568..16fb50c15af6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
@@ -16,21 +16,19 @@
package com.android.systemui.controls.ui
-import android.content.Context
-import android.content.SharedPreferences
import android.test.suitebuilder.annotation.SmallTest
import android.testing.AndroidTestingRunner
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.BroadcastSender
import com.android.systemui.controls.ControlsMetricsLogger
-import com.android.systemui.controls.FakeControlsSettingsRepository
+import com.android.systemui.controls.settings.ControlsSettingsDialogManager
+import com.android.systemui.controls.settings.FakeControlsSettingsRepository
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.settings.UserContextProvider
import com.android.systemui.statusbar.VibratorHelper
-import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.concurrency.DelayableExecutor
-import com.android.systemui.util.settings.SecureSettings
import com.android.wm.shell.TaskViewFactory
import org.junit.Before
import org.junit.Test
@@ -40,8 +38,8 @@ import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.`when`
import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.doNothing
import org.mockito.Mockito.doReturn
-import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.reset
import org.mockito.Mockito.spy
@@ -71,9 +69,9 @@ class ControlActionCoordinatorImplTest : SysuiTestCase() {
@Mock
private lateinit var metricsLogger: ControlsMetricsLogger
@Mock
- private lateinit var secureSettings: SecureSettings
+ private lateinit var featureFlags: FeatureFlags
@Mock
- private lateinit var userContextProvider: UserContextProvider
+ private lateinit var controlsSettingsDialogManager: ControlsSettingsDialogManager
companion object {
fun <T> any(): T = Mockito.any<T>()
@@ -103,23 +101,16 @@ class ControlActionCoordinatorImplTest : SysuiTestCase() {
taskViewFactory,
metricsLogger,
vibratorHelper,
- secureSettings,
- userContextProvider,
- controlsSettingsRepository
+ controlsSettingsRepository,
+ controlsSettingsDialogManager,
+ featureFlags
))
-
- val userContext = mock(Context::class.java)
- val pref = mock(SharedPreferences::class.java)
- `when`(userContextProvider.userContext).thenReturn(userContext)
- `when`(userContext.getSharedPreferences(
- DeviceControlsControllerImpl.PREFS_CONTROLS_FILE, Context.MODE_PRIVATE))
- .thenReturn(pref)
- // Just return 2 so we don't test any Dialog logic which requires a launched activity.
- `when`(pref.getInt(DeviceControlsControllerImpl.PREFS_SETTINGS_DIALOG_ATTEMPTS, 0))
- .thenReturn(2)
+ coordinator.activityContext = mContext
`when`(cvh.cws.ci.controlId).thenReturn(ID)
`when`(cvh.cws.control?.isAuthRequired()).thenReturn(true)
+ `when`(featureFlags.isEnabled(Flags.USE_APP_PANELS)).thenReturn(false)
+
action = spy(coordinator.Action(ID, {}, false, true))
doReturn(action).`when`(coordinator).createAction(any(), any(), anyBoolean(), anyBoolean())
}
@@ -160,15 +151,31 @@ class ControlActionCoordinatorImplTest : SysuiTestCase() {
doReturn(action).`when`(coordinator).createAction(any(), any(), anyBoolean(), anyBoolean())
`when`(keyguardStateController.isShowing()).thenReturn(true)
- `when`(keyguardStateController.isUnlocked()).thenReturn(false)
coordinator.toggle(cvh, "", true)
verify(coordinator).bouncerOrRun(action)
+ verify(controlsSettingsDialogManager).maybeShowDialog(any(), any())
verify(action).invoke()
}
@Test
+ fun testToggleWhenLockedDoesNotTriggerDialog_featureFlagEnabled() {
+ `when`(featureFlags.isEnabled(Flags.USE_APP_PANELS)).thenReturn(true)
+ action = spy(coordinator.Action(ID, {}, false, false))
+ doReturn(action).`when`(coordinator).createAction(any(), any(), anyBoolean(), anyBoolean())
+
+ `when`(keyguardStateController.isShowing()).thenReturn(true)
+ `when`(keyguardStateController.isUnlocked()).thenReturn(false)
+ doNothing().`when`(controlsSettingsDialogManager).maybeShowDialog(any(), any())
+
+ coordinator.toggle(cvh, "", true)
+
+ verify(coordinator).bouncerOrRun(action)
+ verify(controlsSettingsDialogManager, never()).maybeShowDialog(any(), any())
+ }
+
+ @Test
fun testToggleDoesNotRunsWhenLockedAndAuthRequired() {
action = spy(coordinator.Action(ID, {}, false, true))
doReturn(action).`when`(coordinator).createAction(any(), any(), anyBoolean(), anyBoolean())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt
index 48fc46b7e730..9144b13c7f3e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt
@@ -22,7 +22,7 @@ import com.android.internal.widget.LockPatternUtils
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
import com.android.systemui.SysuiTestCase
-import com.android.systemui.controls.FakeControlsSettingsRepository
+import com.android.systemui.controls.settings.FakeControlsSettingsRepository
import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.controls.controller.ControlsTileResourceConfiguration
import com.android.systemui.controls.management.ControlsListingController
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/settings/ControlsSettingsDialogManagerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/settings/ControlsSettingsDialogManagerImplTest.kt
new file mode 100644
index 000000000000..0c9986d82447
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/settings/ControlsSettingsDialogManagerImplTest.kt
@@ -0,0 +1,328 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.controls.settings
+
+import android.content.DialogInterface
+import android.content.SharedPreferences
+import android.database.ContentObserver
+import android.provider.Settings.Secure.LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS
+import android.provider.Settings.Secure.LOCKSCREEN_SHOW_CONTROLS
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.controls.settings.ControlsSettingsDialogManager.Companion.PREFS_SETTINGS_DIALOG_ATTEMPTS
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.settings.UserFileManager
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
+import com.android.systemui.util.FakeSharedPreferences
+import com.android.systemui.util.TestableAlertDialog
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.nullable
+import com.android.systemui.util.settings.FakeSettings
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.Mock
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class ControlsSettingsDialogManagerImplTest : SysuiTestCase() {
+
+ companion object {
+ private const val SETTING_SHOW = LOCKSCREEN_SHOW_CONTROLS
+ private const val SETTING_ACTION = LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS
+ private const val MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG = 2
+ }
+
+ @Mock private lateinit var userFileManager: UserFileManager
+ @Mock private lateinit var userTracker: UserTracker
+ @Mock private lateinit var activityStarter: ActivityStarter
+ @Mock private lateinit var completedRunnable: () -> Unit
+
+ private lateinit var controlsSettingsRepository: FakeControlsSettingsRepository
+ private lateinit var sharedPreferences: FakeSharedPreferences
+ private lateinit var secureSettings: FakeSettings
+
+ private lateinit var underTest: ControlsSettingsDialogManagerImpl
+
+ private var dialog: TestableAlertDialog? = null
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ controlsSettingsRepository = FakeControlsSettingsRepository()
+ sharedPreferences = FakeSharedPreferences()
+ secureSettings = FakeSettings()
+
+ `when`(userTracker.userId).thenReturn(0)
+ secureSettings.userId = userTracker.userId
+ `when`(
+ userFileManager.getSharedPreferences(
+ eq(DeviceControlsControllerImpl.PREFS_CONTROLS_FILE),
+ anyInt(),
+ anyInt()
+ )
+ )
+ .thenReturn(sharedPreferences)
+
+ `when`(activityStarter.dismissKeyguardThenExecute(any(), nullable(), anyBoolean()))
+ .thenAnswer { (it.arguments[0] as ActivityStarter.OnDismissAction).onDismiss() }
+
+ attachRepositoryToSettings()
+ underTest =
+ ControlsSettingsDialogManagerImpl(
+ secureSettings,
+ userFileManager,
+ controlsSettingsRepository,
+ userTracker,
+ activityStarter
+ ) { context, _ -> TestableAlertDialog(context).also { dialog = it } }
+ }
+
+ @After
+ fun tearDown() {
+ underTest.closeDialog()
+ }
+
+ @Test
+ fun dialogNotShownIfPrefsAtMaximum() {
+ sharedPreferences.putAttempts(MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG)
+
+ underTest.maybeShowDialog(context, completedRunnable)
+
+ assertThat(dialog?.isShowing ?: false).isFalse()
+ verify(completedRunnable).invoke()
+ }
+
+ @Test
+ fun dialogNotShownIfSettingsAreTrue() {
+ sharedPreferences.putAttempts(0)
+ secureSettings.putBool(SETTING_SHOW, true)
+ secureSettings.putBool(SETTING_ACTION, true)
+
+ underTest.maybeShowDialog(context, completedRunnable)
+
+ assertThat(dialog?.isShowing ?: false).isFalse()
+ verify(completedRunnable).invoke()
+ }
+
+ @Test
+ fun dialogShownIfAllowTrivialControlsFalse() {
+ sharedPreferences.putAttempts(0)
+ secureSettings.putBool(SETTING_SHOW, true)
+ secureSettings.putBool(SETTING_ACTION, false)
+
+ underTest.maybeShowDialog(context, completedRunnable)
+
+ assertThat(dialog?.isShowing ?: false).isTrue()
+ }
+
+ @Test
+ fun dialogDispossedAfterClosing() {
+ sharedPreferences.putAttempts(0)
+ secureSettings.putBool(SETTING_SHOW, true)
+ secureSettings.putBool(SETTING_ACTION, false)
+
+ underTest.maybeShowDialog(context, completedRunnable)
+ underTest.closeDialog()
+
+ assertThat(dialog?.isShowing ?: false).isFalse()
+ }
+
+ @Test
+ fun dialogNeutralButtonDoesntChangeSetting() {
+ sharedPreferences.putAttempts(0)
+ secureSettings.putBool(SETTING_SHOW, true)
+ secureSettings.putBool(SETTING_ACTION, false)
+
+ underTest.maybeShowDialog(context, completedRunnable)
+ clickButton(DialogInterface.BUTTON_NEUTRAL)
+
+ assertThat(secureSettings.getBool(SETTING_ACTION, false)).isFalse()
+ }
+
+ @Test
+ fun dialogNeutralButtonPutsMaxAttempts() {
+ sharedPreferences.putAttempts(0)
+ secureSettings.putBool(SETTING_SHOW, true)
+ secureSettings.putBool(SETTING_ACTION, false)
+
+ underTest.maybeShowDialog(context, completedRunnable)
+ clickButton(DialogInterface.BUTTON_NEUTRAL)
+
+ assertThat(sharedPreferences.getInt(PREFS_SETTINGS_DIALOG_ATTEMPTS, 0))
+ .isEqualTo(MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG)
+ }
+
+ @Test
+ fun dialogNeutralButtonCallsOnComplete() {
+ sharedPreferences.putAttempts(0)
+ secureSettings.putBool(SETTING_SHOW, true)
+ secureSettings.putBool(SETTING_ACTION, false)
+
+ underTest.maybeShowDialog(context, completedRunnable)
+ clickButton(DialogInterface.BUTTON_NEUTRAL)
+
+ verify(completedRunnable).invoke()
+ }
+
+ @Test
+ fun dialogPositiveButtonChangesSetting() {
+ sharedPreferences.putAttempts(0)
+ secureSettings.putBool(SETTING_SHOW, true)
+ secureSettings.putBool(SETTING_ACTION, false)
+
+ underTest.maybeShowDialog(context, completedRunnable)
+ clickButton(DialogInterface.BUTTON_POSITIVE)
+
+ assertThat(secureSettings.getBool(SETTING_ACTION, false)).isTrue()
+ }
+
+ @Test
+ fun dialogPositiveButtonPutsMaxAttempts() {
+ sharedPreferences.putAttempts(0)
+ secureSettings.putBool(SETTING_SHOW, true)
+ secureSettings.putBool(SETTING_ACTION, false)
+
+ underTest.maybeShowDialog(context, completedRunnable)
+ clickButton(DialogInterface.BUTTON_POSITIVE)
+
+ assertThat(sharedPreferences.getInt(PREFS_SETTINGS_DIALOG_ATTEMPTS, 0))
+ .isEqualTo(MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG)
+ }
+
+ @Test
+ fun dialogPositiveButtonCallsOnComplete() {
+ sharedPreferences.putAttempts(0)
+ secureSettings.putBool(SETTING_SHOW, true)
+ secureSettings.putBool(SETTING_ACTION, false)
+
+ underTest.maybeShowDialog(context, completedRunnable)
+ clickButton(DialogInterface.BUTTON_POSITIVE)
+
+ verify(completedRunnable).invoke()
+ }
+
+ @Test
+ fun dialogCancelDoesntChangeSetting() {
+ sharedPreferences.putAttempts(0)
+ secureSettings.putBool(SETTING_SHOW, true)
+ secureSettings.putBool(SETTING_ACTION, false)
+
+ underTest.maybeShowDialog(context, completedRunnable)
+ dialog?.cancel()
+
+ assertThat(secureSettings.getBool(SETTING_ACTION, false)).isFalse()
+ }
+
+ @Test
+ fun dialogCancelPutsOneExtraAttempt() {
+ val attempts = 0
+ sharedPreferences.putAttempts(attempts)
+ secureSettings.putBool(SETTING_SHOW, true)
+ secureSettings.putBool(SETTING_ACTION, false)
+
+ underTest.maybeShowDialog(context, completedRunnable)
+ dialog?.cancel()
+
+ assertThat(sharedPreferences.getInt(PREFS_SETTINGS_DIALOG_ATTEMPTS, 0))
+ .isEqualTo(attempts + 1)
+ }
+
+ @Test
+ fun dialogCancelCallsOnComplete() {
+ sharedPreferences.putAttempts(0)
+ secureSettings.putBool(SETTING_SHOW, true)
+ secureSettings.putBool(SETTING_ACTION, false)
+
+ underTest.maybeShowDialog(context, completedRunnable)
+ dialog?.cancel()
+
+ verify(completedRunnable).invoke()
+ }
+
+ @Test
+ fun closeDialogDoesNotCallOnComplete() {
+ sharedPreferences.putAttempts(0)
+ secureSettings.putBool(SETTING_SHOW, true)
+ secureSettings.putBool(SETTING_ACTION, false)
+
+ underTest.maybeShowDialog(context, completedRunnable)
+ underTest.closeDialog()
+
+ verify(completedRunnable, never()).invoke()
+ }
+
+ @Test
+ fun dialogPositiveWithBothSettingsFalseTogglesBothSettings() {
+ sharedPreferences.putAttempts(0)
+ secureSettings.putBool(SETTING_SHOW, false)
+ secureSettings.putBool(SETTING_ACTION, false)
+
+ underTest.maybeShowDialog(context, completedRunnable)
+ clickButton(DialogInterface.BUTTON_POSITIVE)
+
+ assertThat(secureSettings.getBool(SETTING_SHOW)).isTrue()
+ assertThat(secureSettings.getBool(SETTING_ACTION)).isTrue()
+ }
+
+ private fun clickButton(which: Int) {
+ dialog?.clickButton(which)
+ }
+
+ private fun attachRepositoryToSettings() {
+ secureSettings.registerContentObserver(
+ SETTING_SHOW,
+ object : ContentObserver(null) {
+ override fun onChange(selfChange: Boolean) {
+ controlsSettingsRepository.setCanShowControlsInLockscreen(
+ secureSettings.getBool(SETTING_SHOW, false)
+ )
+ }
+ }
+ )
+
+ secureSettings.registerContentObserver(
+ SETTING_ACTION,
+ object : ContentObserver(null) {
+ override fun onChange(selfChange: Boolean) {
+ controlsSettingsRepository.setAllowActionOnTrivialControlsInLockscreen(
+ secureSettings.getBool(SETTING_ACTION, false)
+ )
+ }
+ }
+ )
+ }
+
+ private fun SharedPreferences.putAttempts(value: Int) {
+ edit().putInt(PREFS_SETTINGS_DIALOG_ATTEMPTS, value).commit()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ControlsSettingsRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/settings/ControlsSettingsRepositoryImplTest.kt
index 4b88b44c3f03..b904ac14e707 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/ControlsSettingsRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/settings/ControlsSettingsRepositoryImplTest.kt
@@ -15,7 +15,7 @@
*
*/
-package com.android.systemui.controls
+package com.android.systemui.controls.settings
import android.content.pm.UserInfo
import android.provider.Settings
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/FakeControlsSettingsRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/settings/FakeControlsSettingsRepository.kt
index 8a1bed20e700..b6628db14235 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/FakeControlsSettingsRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/settings/FakeControlsSettingsRepository.kt
@@ -15,7 +15,7 @@
*
*/
-package com.android.systemui.controls
+package com.android.systemui.controls.settings
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
index e679b1391c77..779788aa0075 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
@@ -16,18 +16,29 @@
package com.android.systemui.controls.ui
+import android.app.PendingIntent
import android.content.ComponentName
import android.content.Context
+import android.content.pm.ApplicationInfo
+import android.content.pm.ServiceInfo
+import android.os.UserHandle
+import android.service.controls.ControlsProviderService
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
+import android.util.AttributeSet
+import android.view.LayoutInflater
+import android.view.View
import android.widget.FrameLayout
import androidx.test.filters.SmallTest
+import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.controls.ControlsMetricsLogger
+import com.android.systemui.controls.ControlsServiceInfo
import com.android.systemui.controls.CustomIconCache
import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.controls.controller.StructureInfo
import com.android.systemui.controls.management.ControlsListingController
+import com.android.systemui.controls.settings.FakeControlsSettingsRepository
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.settings.UserFileManager
@@ -38,19 +49,26 @@ import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.FakeSharedPreferences
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
import com.android.systemui.util.time.FakeSystemClock
+import com.android.wm.shell.TaskView
import com.android.wm.shell.TaskViewFactory
import com.google.common.truth.Truth.assertThat
import dagger.Lazy
import java.util.Optional
+import java.util.function.Consumer
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.anyString
-import org.mockito.Mockito.mock
+import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.never
+import org.mockito.Mockito.spy
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
@@ -70,9 +88,9 @@ class ControlsUiControllerImplTest : SysuiTestCase() {
@Mock lateinit var userFileManager: UserFileManager
@Mock lateinit var userTracker: UserTracker
@Mock lateinit var taskViewFactory: TaskViewFactory
- @Mock lateinit var activityContext: Context
@Mock lateinit var dumpManager: DumpManager
val sharedPreferences = FakeSharedPreferences()
+ lateinit var controlsSettingsRepository: FakeControlsSettingsRepository
var uiExecutor = FakeExecutor(FakeSystemClock())
var bgExecutor = FakeExecutor(FakeSystemClock())
@@ -83,6 +101,17 @@ class ControlsUiControllerImplTest : SysuiTestCase() {
fun setup() {
MockitoAnnotations.initMocks(this)
+ controlsSettingsRepository = FakeControlsSettingsRepository()
+
+ // This way, it won't be cloned every time `LayoutInflater.fromContext` is called, but we
+ // need to clone it once so we don't modify the original one.
+ mContext.addMockSystemService(
+ Context.LAYOUT_INFLATER_SERVICE,
+ mContext.baseContext
+ .getSystemService(LayoutInflater::class.java)!!
+ .cloneInContext(mContext)
+ )
+
parent = FrameLayout(mContext)
underTest =
@@ -100,6 +129,7 @@ class ControlsUiControllerImplTest : SysuiTestCase() {
userFileManager,
userTracker,
Optional.of(taskViewFactory),
+ controlsSettingsRepository,
dumpManager
)
`when`(
@@ -113,11 +143,12 @@ class ControlsUiControllerImplTest : SysuiTestCase() {
`when`(userFileManager.getSharedPreferences(anyString(), anyInt(), anyInt()))
.thenReturn(sharedPreferences)
`when`(userTracker.userId).thenReturn(0)
+ `when`(userTracker.userHandle).thenReturn(UserHandle.of(0))
}
@Test
fun testGetPreferredStructure() {
- val structureInfo = mock(StructureInfo::class.java)
+ val structureInfo = mock<StructureInfo>()
underTest.getPreferredSelectedItem(listOf(structureInfo))
verify(userFileManager)
.getSharedPreferences(
@@ -189,14 +220,195 @@ class ControlsUiControllerImplTest : SysuiTestCase() {
@Test
fun testPanelDoesNotRefreshControls() {
val panel = SelectedItem.PanelItem("App name", ComponentName("pkg", "cls"))
+ setUpPanel(panel)
+
+ underTest.show(parent, {}, context)
+ verify(controlsController, never()).refreshStatus(any(), any())
+ }
+
+ @Test
+ fun testPanelCallsTaskViewFactoryCreate() {
+ mockLayoutInflater()
+ val panel = SelectedItem.PanelItem("App name", ComponentName("pkg", "cls"))
+ val serviceInfo = setUpPanel(panel)
+
+ underTest.show(parent, {}, context)
+
+ val captor = argumentCaptor<ControlsListingController.ControlsListingCallback>()
+
+ verify(controlsListingController).addCallback(capture(captor))
+
+ captor.value.onServicesUpdated(listOf(serviceInfo))
+ FakeExecutor.exhaustExecutors(uiExecutor, bgExecutor)
+
+ verify(taskViewFactory).create(eq(context), eq(uiExecutor), any())
+ }
+
+ @Test
+ fun testPanelControllerStartActivityWithCorrectArguments() {
+ mockLayoutInflater()
+ controlsSettingsRepository.setAllowActionOnTrivialControlsInLockscreen(true)
+
+ val panel = SelectedItem.PanelItem("App name", ComponentName("pkg", "cls"))
+ val serviceInfo = setUpPanel(panel)
+
+ underTest.show(parent, {}, context)
+
+ val captor = argumentCaptor<ControlsListingController.ControlsListingCallback>()
+
+ verify(controlsListingController).addCallback(capture(captor))
+
+ captor.value.onServicesUpdated(listOf(serviceInfo))
+ FakeExecutor.exhaustExecutors(uiExecutor, bgExecutor)
+
+ val pendingIntent = verifyPanelCreatedAndStartTaskView()
+
+ with(pendingIntent) {
+ assertThat(isActivity).isTrue()
+ assertThat(intent.component).isEqualTo(serviceInfo.panelActivity)
+ assertThat(
+ intent.getBooleanExtra(
+ ControlsProviderService.EXTRA_LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS,
+ false
+ )
+ )
+ .isTrue()
+ }
+ }
+
+ @Test
+ fun testPendingIntentExtrasAreModified() {
+ mockLayoutInflater()
+ controlsSettingsRepository.setAllowActionOnTrivialControlsInLockscreen(true)
+
+ val panel = SelectedItem.PanelItem("App name", ComponentName("pkg", "cls"))
+ val serviceInfo = setUpPanel(panel)
+
+ underTest.show(parent, {}, context)
+
+ val captor = argumentCaptor<ControlsListingController.ControlsListingCallback>()
+
+ verify(controlsListingController).addCallback(capture(captor))
+
+ captor.value.onServicesUpdated(listOf(serviceInfo))
+ FakeExecutor.exhaustExecutors(uiExecutor, bgExecutor)
+
+ val pendingIntent = verifyPanelCreatedAndStartTaskView()
+ assertThat(
+ pendingIntent.intent.getBooleanExtra(
+ ControlsProviderService.EXTRA_LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS,
+ false
+ )
+ )
+ .isTrue()
+
+ underTest.hide()
+
+ clearInvocations(controlsListingController, taskViewFactory)
+ controlsSettingsRepository.setAllowActionOnTrivialControlsInLockscreen(false)
+ underTest.show(parent, {}, context)
+
+ verify(controlsListingController).addCallback(capture(captor))
+ captor.value.onServicesUpdated(listOf(serviceInfo))
+ FakeExecutor.exhaustExecutors(uiExecutor, bgExecutor)
+
+ val newPendingIntent = verifyPanelCreatedAndStartTaskView()
+ assertThat(
+ newPendingIntent.intent.getBooleanExtra(
+ ControlsProviderService.EXTRA_LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS,
+ false
+ )
+ )
+ .isFalse()
+ }
+
+ private fun setUpPanel(panel: SelectedItem.PanelItem): ControlsServiceInfo {
+ val activity = ComponentName("pkg", "activity")
sharedPreferences
.edit()
.putString("controls_component", panel.componentName.flattenToString())
.putString("controls_structure", panel.appName.toString())
.putBoolean("controls_is_panel", true)
.commit()
+ return ControlsServiceInfo(panel.componentName, panel.appName, activity)
+ }
- underTest.show(parent, {}, activityContext)
- verify(controlsController, never()).refreshStatus(any(), any())
+ private fun verifyPanelCreatedAndStartTaskView(): PendingIntent {
+ val taskViewConsumerCaptor = argumentCaptor<Consumer<TaskView>>()
+ verify(taskViewFactory).create(eq(context), eq(uiExecutor), capture(taskViewConsumerCaptor))
+
+ val taskView: TaskView = mock {
+ `when`(this.post(any())).thenAnswer {
+ uiExecutor.execute(it.arguments[0] as Runnable)
+ true
+ }
+ }
+ // calls PanelTaskViewController#launchTaskView
+ taskViewConsumerCaptor.value.accept(taskView)
+ val listenerCaptor = argumentCaptor<TaskView.Listener>()
+ verify(taskView).setListener(any(), capture(listenerCaptor))
+ listenerCaptor.value.onInitialized()
+ FakeExecutor.exhaustExecutors(uiExecutor, bgExecutor)
+
+ val pendingIntentCaptor = argumentCaptor<PendingIntent>()
+ verify(taskView).startActivity(capture(pendingIntentCaptor), any(), any(), any())
+ return pendingIntentCaptor.value
+ }
+
+ private fun ControlsServiceInfo(
+ componentName: ComponentName,
+ label: CharSequence,
+ panelComponentName: ComponentName? = null
+ ): ControlsServiceInfo {
+ val serviceInfo =
+ ServiceInfo().apply {
+ applicationInfo = ApplicationInfo()
+ packageName = componentName.packageName
+ name = componentName.className
+ }
+ return spy(ControlsServiceInfo(mContext, serviceInfo)).apply {
+ `when`(loadLabel()).thenReturn(label)
+ `when`(loadIcon()).thenReturn(mock())
+ `when`(panelActivity).thenReturn(panelComponentName)
+ }
+ }
+
+ private fun mockLayoutInflater() {
+ LayoutInflater.from(context)
+ .setPrivateFactory(
+ object : LayoutInflater.Factory2 {
+ override fun onCreateView(
+ view: View?,
+ name: String,
+ context: Context,
+ attrs: AttributeSet
+ ): View? {
+ return onCreateView(name, context, attrs)
+ }
+
+ override fun onCreateView(
+ name: String,
+ context: Context,
+ attrs: AttributeSet
+ ): View? {
+ if (FrameLayout::class.java.simpleName.equals(name)) {
+ val mock: FrameLayout = mock {
+ `when`(this.context).thenReturn(context)
+ `when`(this.id).thenReturn(R.id.controls_panel)
+ `when`(this.requireViewById<View>(any())).thenCallRealMethod()
+ `when`(this.findViewById<View>(R.id.controls_panel))
+ .thenReturn(this)
+ `when`(this.post(any())).thenAnswer {
+ uiExecutor.execute(it.arguments[0] as Runnable)
+ true
+ }
+ }
+ return mock
+ } else {
+ return null
+ }
+ }
+ }
+ )
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugRestarterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugRestarterTest.kt
index 1e7b1f26ba8c..ed167212c96a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugRestarterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugRestarterTest.kt
@@ -48,22 +48,22 @@ class FeatureFlagsDebugRestarterTest : SysuiTestCase() {
@Test
fun testRestart_ImmediateWhenAsleep() {
whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
- restarter.restart()
- verify(systemExitRestarter).restart()
+ restarter.restartSystemUI()
+ verify(systemExitRestarter).restartSystemUI()
}
@Test
fun testRestart_WaitsForSceenOff() {
whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_AWAKE)
- restarter.restart()
- verify(systemExitRestarter, never()).restart()
+ restarter.restartSystemUI()
+ verify(systemExitRestarter, never()).restartSystemUI()
val captor = ArgumentCaptor.forClass(WakefulnessLifecycle.Observer::class.java)
verify(wakefulnessLifecycle).addObserver(captor.capture())
captor.value.onFinishedGoingToSleep()
- verify(systemExitRestarter).restart()
+ verify(systemExitRestarter).restartSystemUI()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseRestarterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseRestarterTest.kt
index 68ca48dd327d..7d807e2ff207 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseRestarterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseRestarterTest.kt
@@ -63,7 +63,7 @@ class FeatureFlagsReleaseRestarterTest : SysuiTestCase() {
whenever(batteryController.isPluggedIn).thenReturn(true)
assertThat(executor.numPending()).isEqualTo(0)
- restarter.restart()
+ restarter.restartSystemUI()
assertThat(executor.numPending()).isEqualTo(1)
}
@@ -72,11 +72,11 @@ class FeatureFlagsReleaseRestarterTest : SysuiTestCase() {
whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
whenever(batteryController.isPluggedIn).thenReturn(true)
- restarter.restart()
- verify(systemExitRestarter, never()).restart()
+ restarter.restartSystemUI()
+ verify(systemExitRestarter, never()).restartSystemUI()
executor.advanceClockToLast()
executor.runAllReady()
- verify(systemExitRestarter).restart()
+ verify(systemExitRestarter).restartSystemUI()
}
@Test
@@ -85,7 +85,7 @@ class FeatureFlagsReleaseRestarterTest : SysuiTestCase() {
whenever(batteryController.isPluggedIn).thenReturn(true)
assertThat(executor.numPending()).isEqualTo(0)
- restarter.restart()
+ restarter.restartSystemUI()
assertThat(executor.numPending()).isEqualTo(0)
}
@@ -95,7 +95,7 @@ class FeatureFlagsReleaseRestarterTest : SysuiTestCase() {
whenever(batteryController.isPluggedIn).thenReturn(false)
assertThat(executor.numPending()).isEqualTo(0)
- restarter.restart()
+ restarter.restartSystemUI()
assertThat(executor.numPending()).isEqualTo(0)
}
@@ -105,8 +105,8 @@ class FeatureFlagsReleaseRestarterTest : SysuiTestCase() {
whenever(batteryController.isPluggedIn).thenReturn(true)
assertThat(executor.numPending()).isEqualTo(0)
- restarter.restart()
- restarter.restart()
+ restarter.restartSystemUI()
+ restarter.restartSystemUI()
assertThat(executor.numPending()).isEqualTo(1)
}
@@ -115,7 +115,7 @@ class FeatureFlagsReleaseRestarterTest : SysuiTestCase() {
whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_AWAKE)
whenever(batteryController.isPluggedIn).thenReturn(true)
assertThat(executor.numPending()).isEqualTo(0)
- restarter.restart()
+ restarter.restartSystemUI()
val captor = ArgumentCaptor.forClass(WakefulnessLifecycle.Observer::class.java)
verify(wakefulnessLifecycle).addObserver(captor.capture())
@@ -131,7 +131,7 @@ class FeatureFlagsReleaseRestarterTest : SysuiTestCase() {
whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
whenever(batteryController.isPluggedIn).thenReturn(false)
assertThat(executor.numPending()).isEqualTo(0)
- restarter.restart()
+ restarter.restartSystemUI()
val captor =
ArgumentCaptor.forClass(BatteryController.BatteryStateChangeCallback::class.java)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index d17e3744edc6..798839dcc1f6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -16,6 +16,7 @@
package com.android.systemui.keyguard;
+import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY;
import static android.view.WindowManagerPolicyConstants.OFF_BECAUSE_OF_USER;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW;
@@ -34,6 +35,7 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.IActivityManager;
import android.app.admin.DevicePolicyManager;
import android.app.trust.TrustManager;
import android.os.PowerManager;
@@ -41,6 +43,11 @@ import android.os.PowerManager.WakeLock;
import android.telephony.TelephonyManager;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import android.view.IRemoteAnimationFinishedCallback;
+import android.view.RemoteAnimationTarget;
+import android.view.View;
+import android.view.ViewRootImpl;
+import android.view.WindowManager;
import androidx.test.filters.SmallTest;
@@ -52,21 +59,27 @@ import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.mediator.ScreenOnCoordinator;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.biometrics.AuthController;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.classifier.FalsingCollectorFake;
+import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.settings.UserTracker;
+import com.android.systemui.shade.NotificationShadeWindowControllerImpl;
import com.android.systemui.shade.ShadeController;
+import com.android.systemui.shade.ShadeExpansionStateManager;
import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
import com.android.systemui.statusbar.phone.ScrimController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.util.DeviceConfigProxy;
@@ -80,8 +93,6 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import dagger.Lazy;
-
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@SmallTest
@@ -96,11 +107,15 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
private @Mock BroadcastDispatcher mBroadcastDispatcher;
private @Mock DismissCallbackRegistry mDismissCallbackRegistry;
private @Mock DumpManager mDumpManager;
+ private @Mock WindowManager mWindowManager;
+ private @Mock IActivityManager mActivityManager;
+ private @Mock ConfigurationController mConfigurationController;
private @Mock PowerManager mPowerManager;
private @Mock TrustManager mTrustManager;
private @Mock UserSwitcherController mUserSwitcherController;
private @Mock NavigationModeController mNavigationModeController;
private @Mock KeyguardDisplayManager mKeyguardDisplayManager;
+ private @Mock KeyguardBypassController mKeyguardBypassController;
private @Mock DozeParameters mDozeParameters;
private @Mock SysuiStatusBarStateController mStatusBarStateController;
private @Mock KeyguardStateController mKeyguardStateController;
@@ -110,10 +125,13 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
private @Mock InteractionJankMonitor mInteractionJankMonitor;
private @Mock ScreenOnCoordinator mScreenOnCoordinator;
private @Mock ShadeController mShadeController;
- private @Mock Lazy<NotificationShadeWindowController> mNotificationShadeWindowControllerLazy;
+ private NotificationShadeWindowController mNotificationShadeWindowController;
private @Mock DreamOverlayStateController mDreamOverlayStateController;
private @Mock ActivityLaunchAnimator mActivityLaunchAnimator;
private @Mock ScrimController mScrimController;
+ private @Mock SysuiColorExtractor mColorExtractor;
+ private @Mock AuthController mAuthController;
+ private @Mock ShadeExpansionStateManager mShadeExpansionStateManager;
private DeviceConfigProxy mDeviceConfig = new DeviceConfigProxyFake();
private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
@@ -130,6 +148,14 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
when(mPowerManager.newWakeLock(anyInt(), any())).thenReturn(mock(WakeLock.class));
when(mInteractionJankMonitor.begin(any(), anyInt())).thenReturn(true);
when(mInteractionJankMonitor.end(anyInt())).thenReturn(true);
+ final ViewRootImpl testViewRoot = mock(ViewRootImpl.class);
+ when(testViewRoot.getView()).thenReturn(mock(View.class));
+ when(mStatusBarKeyguardViewManager.getViewRootImpl()).thenReturn(testViewRoot);
+ mNotificationShadeWindowController = new NotificationShadeWindowControllerImpl(mContext,
+ mWindowManager, mActivityManager, mDozeParameters, mStatusBarStateController,
+ mConfigurationController, mViewMediator, mKeyguardBypassController,
+ mColorExtractor, mDumpManager, mKeyguardStateController,
+ mScreenOffAnimationController, mAuthController, mShadeExpansionStateManager);
createAndStartViewMediator();
}
@@ -287,6 +313,23 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
verify(mCentralSurfaces).updateIsKeyguard();
}
+ @Test
+ @TestableLooper.RunWithLooper(setAsMainLooper = true)
+ public void testStartKeyguardExitAnimation_expectSurfaceBehindRemoteAnimation() {
+ RemoteAnimationTarget[] apps = new RemoteAnimationTarget[]{
+ mock(RemoteAnimationTarget.class)
+ };
+ RemoteAnimationTarget[] wallpapers = new RemoteAnimationTarget[]{
+ mock(RemoteAnimationTarget.class)
+ };
+ IRemoteAnimationFinishedCallback callback = mock(IRemoteAnimationFinishedCallback.class);
+
+ mViewMediator.startKeyguardExitAnimation(TRANSIT_OLD_KEYGUARD_GOING_AWAY, apps, wallpapers,
+ null, callback);
+ TestableLooper.get(this).processAllMessages();
+ assertTrue(mViewMediator.isAnimatingBetweenKeyguardAndSurfaceBehind());
+ }
+
private void createAndStartViewMediator() {
mViewMediator = new KeyguardViewMediator(
mContext,
@@ -315,7 +358,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
mInteractionJankMonitor,
mDreamOverlayStateController,
() -> mShadeController,
- mNotificationShadeWindowControllerLazy,
+ () -> mNotificationShadeWindowController,
() -> mActivityLaunchAnimator,
() -> mScrimController);
mViewMediator.start();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
index 13fc9fcf834b..5deac1924ab7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -16,10 +16,13 @@
package com.android.systemui.keyguard.data.repository
+import android.graphics.Point
+import android.hardware.biometrics.BiometricSourceType
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.AuthController
import com.android.systemui.common.shared.model.Position
import com.android.systemui.doze.DozeHost
import com.android.systemui.doze.DozeMachine
@@ -27,9 +30,11 @@ import com.android.systemui.doze.DozeTransitionCallback
import com.android.systemui.doze.DozeTransitionListener
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
import com.android.systemui.keyguard.shared.model.DozeStateModel
import com.android.systemui.keyguard.shared.model.DozeTransitionModel
import com.android.systemui.keyguard.shared.model.WakefulnessModel
+import com.android.systemui.keyguard.shared.model.WakefulnessState
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.phone.BiometricUnlockController
import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -57,9 +62,10 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
@Mock private lateinit var dozeHost: DozeHost
@Mock private lateinit var keyguardStateController: KeyguardStateController
@Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
- @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@Mock private lateinit var biometricUnlockController: BiometricUnlockController
@Mock private lateinit var dozeTransitionListener: DozeTransitionListener
+ @Mock private lateinit var authController: AuthController
+ @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
private lateinit var underTest: KeyguardRepositoryImpl
@@ -76,6 +82,7 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
keyguardStateController,
keyguardUpdateMonitor,
dozeTransitionListener,
+ authController,
)
}
@@ -198,7 +205,7 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
fun dozeAmount() =
runTest(UnconfinedTestDispatcher()) {
val values = mutableListOf<Float>()
- val job = underTest.dozeAmount.onEach(values::add).launchIn(this)
+ val job = underTest.linearDozeAmount.onEach(values::add).launchIn(this)
val captor = argumentCaptor<StatusBarStateController.StateListener>()
verify(statusBarStateController).addCallback(captor.capture())
@@ -207,7 +214,7 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
captor.value.onDozeAmountChanged(0.498f, 0.5f)
captor.value.onDozeAmountChanged(0.661f, 0.65f)
- assertThat(values).isEqualTo(listOf(0f, 0.4f, 0.5f, 0.65f))
+ assertThat(values).isEqualTo(listOf(0f, 0.433f, 0.498f, 0.661f))
job.cancel()
verify(statusBarStateController).removeCallback(captor.value)
@@ -217,25 +224,36 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
fun wakefulness() =
runTest(UnconfinedTestDispatcher()) {
val values = mutableListOf<WakefulnessModel>()
- val job = underTest.wakefulnessState.onEach(values::add).launchIn(this)
+ val job = underTest.wakefulness.onEach(values::add).launchIn(this)
val captor = argumentCaptor<WakefulnessLifecycle.Observer>()
verify(wakefulnessLifecycle).addObserver(captor.capture())
+ whenever(wakefulnessLifecycle.wakefulness)
+ .thenReturn(WakefulnessLifecycle.WAKEFULNESS_WAKING)
captor.value.onStartedWakingUp()
+
+ whenever(wakefulnessLifecycle.wakefulness)
+ .thenReturn(WakefulnessLifecycle.WAKEFULNESS_AWAKE)
captor.value.onFinishedWakingUp()
+
+ whenever(wakefulnessLifecycle.wakefulness)
+ .thenReturn(WakefulnessLifecycle.WAKEFULNESS_GOING_TO_SLEEP)
captor.value.onStartedGoingToSleep()
+
+ whenever(wakefulnessLifecycle.wakefulness)
+ .thenReturn(WakefulnessLifecycle.WAKEFULNESS_ASLEEP)
captor.value.onFinishedGoingToSleep()
- assertThat(values)
+ assertThat(values.map { it.state })
.isEqualTo(
listOf(
// Initial value will be ASLEEP
- WakefulnessModel.ASLEEP,
- WakefulnessModel.STARTING_TO_WAKE,
- WakefulnessModel.AWAKE,
- WakefulnessModel.STARTING_TO_SLEEP,
- WakefulnessModel.ASLEEP,
+ WakefulnessState.ASLEEP,
+ WakefulnessState.STARTING_TO_WAKE,
+ WakefulnessState.AWAKE,
+ WakefulnessState.STARTING_TO_SLEEP,
+ WakefulnessState.ASLEEP,
)
)
@@ -329,14 +347,20 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
val captor = argumentCaptor<BiometricUnlockController.BiometricModeListener>()
verify(biometricUnlockController).addBiometricModeListener(captor.capture())
- captor.value.onModeChanged(BiometricUnlockController.MODE_NONE)
- captor.value.onModeChanged(BiometricUnlockController.MODE_WAKE_AND_UNLOCK)
- captor.value.onModeChanged(BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING)
- captor.value.onModeChanged(BiometricUnlockController.MODE_SHOW_BOUNCER)
- captor.value.onModeChanged(BiometricUnlockController.MODE_ONLY_WAKE)
- captor.value.onModeChanged(BiometricUnlockController.MODE_UNLOCK_COLLAPSING)
- captor.value.onModeChanged(BiometricUnlockController.MODE_DISMISS_BOUNCER)
- captor.value.onModeChanged(BiometricUnlockController.MODE_WAKE_AND_UNLOCK_FROM_DREAM)
+ listOf(
+ BiometricUnlockController.MODE_NONE,
+ BiometricUnlockController.MODE_WAKE_AND_UNLOCK,
+ BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING,
+ BiometricUnlockController.MODE_SHOW_BOUNCER,
+ BiometricUnlockController.MODE_ONLY_WAKE,
+ BiometricUnlockController.MODE_UNLOCK_COLLAPSING,
+ BiometricUnlockController.MODE_DISMISS_BOUNCER,
+ BiometricUnlockController.MODE_WAKE_AND_UNLOCK_FROM_DREAM,
+ )
+ .forEach {
+ whenever(biometricUnlockController.mode).thenReturn(it)
+ captor.value.onModeChanged(it)
+ }
assertThat(values)
.isEqualTo(
@@ -420,4 +444,104 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
job.cancel()
verify(dozeTransitionListener).removeCallback(listener)
}
+
+ @Test
+ fun fingerprintSensorLocation() =
+ runTest(UnconfinedTestDispatcher()) {
+ val values = mutableListOf<Point?>()
+ val job = underTest.fingerprintSensorLocation.onEach(values::add).launchIn(this)
+
+ val captor = argumentCaptor<AuthController.Callback>()
+ verify(authController).addCallback(captor.capture())
+
+ // An initial, null value should be initially emitted so that flows combined with this
+ // one
+ // emit values immediately. The sensor location is expected to be nullable, so anyone
+ // consuming it should handle that properly.
+ assertThat(values).isEqualTo(listOf(null))
+
+ listOf(Point(500, 500), Point(0, 0), null, Point(250, 250))
+ .onEach {
+ whenever(authController.fingerprintSensorLocation).thenReturn(it)
+ captor.value.onFingerprintLocationChanged()
+ }
+ .also { dispatchedSensorLocations ->
+ assertThat(values).isEqualTo(listOf(null) + dispatchedSensorLocations)
+ }
+
+ job.cancel()
+ }
+
+ @Test
+ fun faceSensorLocation() =
+ runTest(UnconfinedTestDispatcher()) {
+ val values = mutableListOf<Point?>()
+ val job = underTest.faceSensorLocation.onEach(values::add).launchIn(this)
+
+ val captor = argumentCaptor<AuthController.Callback>()
+ verify(authController).addCallback(captor.capture())
+
+ // An initial, null value should be initially emitted so that flows combined with this
+ // one
+ // emit values immediately. The sensor location is expected to be nullable, so anyone
+ // consuming it should handle that properly.
+ assertThat(values).isEqualTo(listOf(null))
+
+ listOf(
+ Point(500, 500),
+ Point(0, 0),
+ null,
+ Point(250, 250),
+ )
+ .onEach {
+ whenever(authController.faceSensorLocation).thenReturn(it)
+ captor.value.onFaceSensorLocationChanged()
+ }
+ .also { dispatchedSensorLocations ->
+ assertThat(values).isEqualTo(listOf(null) + dispatchedSensorLocations)
+ }
+
+ job.cancel()
+ }
+
+ @Test
+ fun biometricUnlockSource() =
+ runTest(UnconfinedTestDispatcher()) {
+ val values = mutableListOf<BiometricUnlockSource?>()
+ val job = underTest.biometricUnlockSource.onEach(values::add).launchIn(this)
+
+ val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
+ verify(keyguardUpdateMonitor).registerCallback(captor.capture())
+
+ // An initial, null value should be initially emitted so that flows combined with this
+ // one
+ // emit values immediately. The biometric unlock source is expected to be nullable, so
+ // anyone consuming it should handle that properly.
+ assertThat(values).isEqualTo(listOf(null))
+
+ listOf(
+ BiometricSourceType.FINGERPRINT,
+ BiometricSourceType.IRIS,
+ null,
+ BiometricSourceType.FACE,
+ BiometricSourceType.FINGERPRINT,
+ )
+ .onEach { biometricSourceType ->
+ captor.value.onBiometricAuthenticated(0, biometricSourceType, false)
+ }
+
+ assertThat(values)
+ .isEqualTo(
+ listOf(
+ null,
+ BiometricUnlockSource.FINGERPRINT_SENSOR,
+ BiometricUnlockSource.FACE_SENSOR,
+ null,
+ BiometricUnlockSource.FACE_SENSOR,
+ BiometricUnlockSource.FINGERPRINT_SENSOR,
+ )
+ )
+
+ job.cancel()
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
index 2b03722f9f31..ce9c1da422f5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
@@ -22,6 +22,7 @@ import android.util.Log
import android.util.Log.TerribleFailure
import android.util.Log.TerribleFailureHandler
import android.view.Choreographer.FrameCallback
+import androidx.test.filters.FlakyTest
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.Interpolators
@@ -97,6 +98,7 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() {
}
@Test
+ @FlakyTest(bugId = 260213291)
fun `starting second transition will cancel the first transition`() {
runBlocking(IMMEDIATE) {
val (animator, provider) = setupAnimator(this)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt
new file mode 100644
index 000000000000..d2db910ad443
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import android.graphics.Point
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
+import com.android.systemui.statusbar.CircleReveal
+import com.android.systemui.statusbar.LightRevealEffect
+import junit.framework.Assert.assertEquals
+import junit.framework.Assert.assertFalse
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(JUnit4::class)
+class LightRevealScrimRepositoryTest : SysuiTestCase() {
+ private lateinit var fakeKeyguardRepository: FakeKeyguardRepository
+ private lateinit var underTest: LightRevealScrimRepositoryImpl
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ fakeKeyguardRepository = FakeKeyguardRepository()
+ underTest = LightRevealScrimRepositoryImpl(fakeKeyguardRepository, context)
+ }
+
+ @Test
+ fun `nextRevealEffect - effect switches between default and biometric with no dupes`() =
+ runTest {
+ val values = mutableListOf<LightRevealEffect>()
+ val job = launch { underTest.revealEffect.collect { values.add(it) } }
+
+ // We should initially emit the default reveal effect.
+ runCurrent()
+ values.assertEffectsMatchPredicates({ it == DEFAULT_REVEAL_EFFECT })
+
+ // The source and sensor locations are still null, so we should still be using the
+ // default reveal despite a biometric unlock.
+ fakeKeyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK)
+
+ runCurrent()
+ values.assertEffectsMatchPredicates({ it == DEFAULT_REVEAL_EFFECT },)
+
+ // We got a source but still have no sensor locations, so should be sticking with
+ // the default effect.
+ fakeKeyguardRepository.setBiometricUnlockSource(
+ BiometricUnlockSource.FINGERPRINT_SENSOR
+ )
+
+ runCurrent()
+ values.assertEffectsMatchPredicates({ it == DEFAULT_REVEAL_EFFECT },)
+
+ // We got a location for the face sensor, but we unlocked with fingerprint.
+ val faceLocation = Point(250, 0)
+ fakeKeyguardRepository.setFaceSensorLocation(faceLocation)
+
+ runCurrent()
+ values.assertEffectsMatchPredicates({ it == DEFAULT_REVEAL_EFFECT },)
+
+ // Now we have fingerprint sensor locations, and wake and unlock via fingerprint.
+ val fingerprintLocation = Point(500, 500)
+ fakeKeyguardRepository.setFingerprintSensorLocation(fingerprintLocation)
+ fakeKeyguardRepository.setBiometricUnlockSource(
+ BiometricUnlockSource.FINGERPRINT_SENSOR
+ )
+ fakeKeyguardRepository.setBiometricUnlockState(
+ BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING
+ )
+
+ // We should now have switched to the circle reveal, at the fingerprint location.
+ runCurrent()
+ values.assertEffectsMatchPredicates(
+ { it == DEFAULT_REVEAL_EFFECT },
+ {
+ it is CircleReveal &&
+ it.centerX == fingerprintLocation.x &&
+ it.centerY == fingerprintLocation.y
+ },
+ )
+
+ // Subsequent wake and unlocks should not emit duplicate, identical CircleReveals.
+ val valuesPrevSize = values.size
+ fakeKeyguardRepository.setBiometricUnlockState(
+ BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING
+ )
+ fakeKeyguardRepository.setBiometricUnlockState(
+ BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM
+ )
+ assertEquals(valuesPrevSize, values.size)
+
+ // Non-biometric unlock, we should return to the default reveal.
+ fakeKeyguardRepository.setBiometricUnlockState(BiometricUnlockModel.NONE)
+
+ runCurrent()
+ values.assertEffectsMatchPredicates(
+ { it == DEFAULT_REVEAL_EFFECT },
+ {
+ it is CircleReveal &&
+ it.centerX == fingerprintLocation.x &&
+ it.centerY == fingerprintLocation.y
+ },
+ { it == DEFAULT_REVEAL_EFFECT },
+ )
+
+ // We already have a face location, so switching to face source should update the
+ // CircleReveal.
+ fakeKeyguardRepository.setBiometricUnlockSource(BiometricUnlockSource.FACE_SENSOR)
+ runCurrent()
+ fakeKeyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK)
+ runCurrent()
+
+ values.assertEffectsMatchPredicates(
+ { it == DEFAULT_REVEAL_EFFECT },
+ {
+ it is CircleReveal &&
+ it.centerX == fingerprintLocation.x &&
+ it.centerY == fingerprintLocation.y
+ },
+ { it == DEFAULT_REVEAL_EFFECT },
+ {
+ it is CircleReveal &&
+ it.centerX == faceLocation.x &&
+ it.centerY == faceLocation.y
+ },
+ )
+
+ job.cancel()
+ }
+
+ /**
+ * Asserts that the list of LightRevealEffects satisfies the list of predicates, in order, with
+ * no leftover elements.
+ */
+ private fun List<LightRevealEffect>.assertEffectsMatchPredicates(
+ vararg predicates: (LightRevealEffect) -> Boolean
+ ) {
+ println(this)
+ assertEquals(predicates.size, this.size)
+
+ assertFalse(
+ zip(predicates) { effect, predicate -> predicate(effect) }.any { matched -> !matched }
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt
new file mode 100644
index 000000000000..31662145dfbe
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.FakeLightRevealScrimRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.statusbar.LightRevealEffect
+import com.android.systemui.statusbar.LightRevealScrim
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(JUnit4::class)
+class LightRevealScrimInteractorTest : SysuiTestCase() {
+ private val fakeKeyguardTransitionRepository = FakeKeyguardTransitionRepository()
+ private val fakeLightRevealScrimRepository = FakeLightRevealScrimRepository()
+
+ private val keyguardTransitionInteractor =
+ KeyguardTransitionInteractor(fakeKeyguardTransitionRepository)
+
+ private lateinit var underTest: LightRevealScrimInteractor
+
+ private val reveal1 =
+ object : LightRevealEffect {
+ override fun setRevealAmountOnScrim(amount: Float, scrim: LightRevealScrim) {}
+ }
+
+ private val reveal2 =
+ object : LightRevealEffect {
+ override fun setRevealAmountOnScrim(amount: Float, scrim: LightRevealScrim) {}
+ }
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ underTest =
+ LightRevealScrimInteractor(
+ fakeKeyguardTransitionRepository,
+ keyguardTransitionInteractor,
+ fakeLightRevealScrimRepository
+ )
+ }
+
+ @Test
+ fun `lightRevealEffect - does not change during keyguard transition`() =
+ runTest(UnconfinedTestDispatcher()) {
+ val values = mutableListOf<LightRevealEffect>()
+ val job = underTest.lightRevealEffect.onEach(values::add).launchIn(this)
+
+ fakeLightRevealScrimRepository.setRevealEffect(reveal1)
+
+ // The reveal effect shouldn't emit anything until a keyguard transition starts.
+ assertEquals(values.size, 0)
+
+ // Once it starts, it should emit reveal1.
+ fakeKeyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(transitionState = TransitionState.STARTED)
+ )
+ assertEquals(values, listOf(reveal1))
+
+ // Until the next transition starts, reveal2 should not be emitted.
+ fakeLightRevealScrimRepository.setRevealEffect(reveal2)
+ fakeKeyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(transitionState = TransitionState.RUNNING)
+ )
+ fakeKeyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(transitionState = TransitionState.FINISHED)
+ )
+ assertEquals(values, listOf(reveal1))
+ fakeKeyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(transitionState = TransitionState.STARTED)
+ )
+ assertEquals(values, listOf(reveal1, reveal2))
+
+ job.cancel()
+ }
+
+ @Test
+ fun `revealAmount - inverted when appropriate`() =
+ runTest(UnconfinedTestDispatcher()) {
+ val values = mutableListOf<Float>()
+ val job = underTest.revealAmount.onEach(values::add).launchIn(this)
+
+ fakeKeyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ value = 0.3f
+ )
+ )
+
+ assertEquals(values, listOf(0.3f))
+
+ fakeKeyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ value = 0.3f
+ )
+ )
+
+ assertEquals(values, listOf(0.3f, 0.7f))
+
+ job.cancel()
+ }
+
+ @Test
+ fun `revealAmount - ignores transitions that do not affect reveal amount`() =
+ runTest(UnconfinedTestDispatcher()) {
+ val values = mutableListOf<Float>()
+ val job = underTest.revealAmount.onEach(values::add).launchIn(this)
+
+ fakeKeyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(from = KeyguardState.DOZING, to = KeyguardState.AOD, value = 0.3f)
+ )
+
+ assertEquals(values, emptyList<Float>())
+
+ fakeKeyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(from = KeyguardState.AOD, to = KeyguardState.DOZING, value = 0.3f)
+ )
+
+ assertEquals(values, emptyList<Float>())
+
+ job.cancel()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/SessionTrackerTest.java b/packages/SystemUI/tests/src/com/android/systemui/log/SessionTrackerTest.java
index dc5522efe406..aa54a1cdf579 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/log/SessionTrackerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/log/SessionTrackerTest.java
@@ -23,8 +23,10 @@ import static android.app.StatusBarManager.SESSION_KEYGUARD;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNull;
+import static org.junit.Assert.assertNotEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -171,6 +173,34 @@ public class SessionTrackerTest extends SysuiTestCase {
}
@Test
+ public void testKeyguardSessionOnDeviceStartsSleepingTwiceInARow_startsNewKeyguardSession()
+ throws RemoteException {
+ // GIVEN session tracker started w/o any sessions
+ mSessionTracker.start();
+ captureKeyguardUpdateMonitorCallback();
+
+ // WHEN device starts going to sleep
+ mKeyguardUpdateMonitorCallback.onStartedGoingToSleep(0);
+
+ // THEN the keyguard session has a session id
+ final InstanceId firstSessionId = mSessionTracker.getSessionId(SESSION_KEYGUARD);
+ assertNotNull(firstSessionId);
+
+ // WHEN device starts going to sleep a second time
+ mKeyguardUpdateMonitorCallback.onStartedGoingToSleep(0);
+
+ // THEN there's a new keyguard session with a unique session id
+ final InstanceId secondSessionId = mSessionTracker.getSessionId(SESSION_KEYGUARD);
+ assertNotNull(secondSessionId);
+ assertNotEquals(firstSessionId, secondSessionId);
+
+ // THEN session start event gets sent to status bar service twice (once per going to
+ // sleep signal)
+ verify(mStatusBarService, times(2)).onSessionStarted(
+ eq(SESSION_KEYGUARD), any(InstanceId.class));
+ }
+
+ @Test
public void testKeyguardSessionOnKeyguardShowingChange() throws RemoteException {
// GIVEN session tracker started w/o any sessions
mSessionTracker.start();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
index 761773b1a345..fdef34449adf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
@@ -702,7 +702,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
}
@Test
- fun bind_seekBarDisabled_noActions_seekBarVisibilityIsSetToGone() {
+ fun bind_seekBarDisabled_noActions_seekBarVisibilityIsSetToInvisible() {
useRealConstraintSets()
val state = mediaData.copy(semanticActions = MediaButton())
@@ -711,7 +711,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
player.bindPlayer(state, PACKAGE)
- assertThat(expandedSet.getVisibility(seekBar.id)).isEqualTo(ConstraintSet.GONE)
+ assertThat(expandedSet.getVisibility(seekBar.id)).isEqualTo(ConstraintSet.INVISIBLE)
}
@Test
@@ -741,7 +741,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
}
@Test
- fun seekBarChangesToDisabledAfterBind_noActions_seekBarChangesToGone() {
+ fun seekBarChangesToDisabledAfterBind_noActions_seekBarChangesToInvisible() {
useRealConstraintSets()
val state = mediaData.copy(semanticActions = MediaButton())
@@ -752,7 +752,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
getEnabledChangeListener().onEnabledChanged(enabled = false)
- assertThat(expandedSet.getVisibility(seekBar.id)).isEqualTo(ConstraintSet.GONE)
+ assertThat(expandedSet.getVisibility(seekBar.id)).isEqualTo(ConstraintSet.INVISIBLE)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
index 5f643363bd2e..5c0f0fee096a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
@@ -38,6 +38,8 @@ import com.android.settingslib.media.MediaDevice;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.google.common.collect.ImmutableList;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -73,11 +75,6 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
@Before
public void setUp() {
- mMediaOutputAdapter = new MediaOutputAdapter(mMediaOutputController);
- mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
- .onCreateViewHolder(new LinearLayout(mContext), 0);
- mSpyMediaOutputSeekbar = spy(mViewHolder.mSeekBar);
-
when(mMediaOutputController.getMediaDevices()).thenReturn(mMediaDevices);
when(mMediaOutputController.hasAdjustVolumeUserRestriction()).thenReturn(false);
when(mMediaOutputController.isAnyDeviceTransferring()).thenReturn(false);
@@ -85,6 +82,7 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
when(mMediaOutputController.getDeviceIconCompat(mMediaDevice2)).thenReturn(mIconCompat);
when(mMediaOutputController.getCurrentConnectedMediaDevice()).thenReturn(mMediaDevice1);
when(mMediaOutputController.isActiveRemoteDevice(mMediaDevice1)).thenReturn(true);
+ when(mMediaOutputController.isAdvancedLayoutSupported()).thenReturn(false);
when(mIconCompat.toIcon(mContext)).thenReturn(mIcon);
when(mMediaDevice1.getName()).thenReturn(TEST_DEVICE_NAME_1);
when(mMediaDevice1.getId()).thenReturn(TEST_DEVICE_ID_1);
@@ -96,6 +94,11 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
LocalMediaManager.MediaDeviceState.STATE_DISCONNECTED);
mMediaDevices.add(mMediaDevice1);
mMediaDevices.add(mMediaDevice2);
+
+ mMediaOutputAdapter = new MediaOutputAdapter(mMediaOutputController);
+ mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
+ .onCreateViewHolder(new LinearLayout(mContext), 0);
+ mSpyMediaOutputSeekbar = spy(mViewHolder.mSeekBar);
}
@Test
@@ -169,6 +172,63 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
}
@Test
+ public void advanced_onBindViewHolder_bindNonRemoteConnectedDevice_verifyView() {
+ when(mMediaOutputController.isAdvancedLayoutSupported()).thenReturn(true);
+ when(mMediaOutputController.isActiveRemoteDevice(mMediaDevice1)).thenReturn(false);
+ mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
+ .onCreateViewHolder(new LinearLayout(mContext), 0);
+ mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
+
+ assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mTitleText.getText().toString()).isEqualTo(TEST_DEVICE_NAME_1);
+ assertThat(mViewHolder.mSubTitleText.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mViewHolder.mTwoLineLayout.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.VISIBLE);
+ }
+
+ @Test
+ public void advanced_onBindViewHolder_bindConnectedRemoteDevice_verifyView() {
+ when(mMediaOutputController.isAdvancedLayoutSupported()).thenReturn(true);
+ when(mMediaOutputController.getSelectableMediaDevice()).thenReturn(
+ ImmutableList.of(mMediaDevice2));
+ when(mMediaOutputController.isCurrentConnectedDeviceRemote()).thenReturn(true);
+ mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
+ .onCreateViewHolder(new LinearLayout(mContext), 0);
+ mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
+
+ assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mTitleText.getText().toString()).isEqualTo(TEST_DEVICE_NAME_1);
+ assertThat(mViewHolder.mSubTitleText.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mTwoLineLayout.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mEndTouchArea.getVisibility()).isEqualTo(View.VISIBLE);
+ }
+
+ @Test
+ public void advanced_onBindViewHolder_bindSingleConnectedRemoteDevice_verifyView() {
+ when(mMediaOutputController.isAdvancedLayoutSupported()).thenReturn(true);
+ when(mMediaOutputController.getSelectableMediaDevice()).thenReturn(
+ ImmutableList.of());
+ when(mMediaOutputController.isCurrentConnectedDeviceRemote()).thenReturn(true);
+ mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
+ .onCreateViewHolder(new LinearLayout(mContext), 0);
+ mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
+
+ assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mTitleText.getText().toString()).isEqualTo(TEST_DEVICE_NAME_1);
+ assertThat(mViewHolder.mSubTitleText.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mViewHolder.mTwoLineLayout.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mEndTouchArea.getVisibility()).isEqualTo(View.GONE);
+ }
+
+ @Test
public void onBindViewHolder_bindConnectedDeviceWithMutingExpectedDeviceExist_verifyView() {
when(mMediaOutputController.hasMutingExpectedDevice()).thenReturn(true);
when(mMediaOutputController.isCurrentConnectedDeviceRemote()).thenReturn(false);
@@ -352,6 +412,38 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
}
@Test
+ public void advanced_onGroupActionTriggered_clicksEndAreaOfSelectableDevice_triggerGrouping() {
+ when(mMediaOutputController.isAdvancedLayoutSupported()).thenReturn(true);
+ List<MediaDevice> selectableDevices = new ArrayList<>();
+ selectableDevices.add(mMediaDevice2);
+ when(mMediaOutputController.getSelectableMediaDevice()).thenReturn(selectableDevices);
+ mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
+ .onCreateViewHolder(new LinearLayout(mContext), 0);
+ mMediaOutputAdapter.onBindViewHolder(mViewHolder, 1);
+
+ mViewHolder.mEndTouchArea.performClick();
+
+ verify(mMediaOutputController).addDeviceToPlayMedia(mMediaDevice2);
+ }
+
+ @Test
+ public void advanced_onGroupActionTriggered_clickSelectedRemoteDevice_triggerUngrouping() {
+ when(mMediaOutputController.isAdvancedLayoutSupported()).thenReturn(true);
+ when(mMediaOutputController.getSelectableMediaDevice()).thenReturn(
+ ImmutableList.of(mMediaDevice2));
+ when(mMediaOutputController.getDeselectableMediaDevice()).thenReturn(
+ ImmutableList.of(mMediaDevice1));
+ when(mMediaOutputController.isCurrentConnectedDeviceRemote()).thenReturn(true);
+ mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
+ .onCreateViewHolder(new LinearLayout(mContext), 0);
+ mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
+
+ mViewHolder.mEndTouchArea.performClick();
+
+ verify(mMediaOutputController).removeDeviceFromPlayMedia(mMediaDevice1);
+ }
+
+ @Test
public void onItemClick_onGroupActionTriggered_verifySeekbarDisabled() {
when(mMediaOutputController.getSelectedMediaDevice()).thenReturn(mMediaDevices);
List<MediaDevice> selectableDevices = new ArrayList<>();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
index 9be201e99b2b..094d69a9d392 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
@@ -51,6 +51,7 @@ import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.broadcast.BroadcastSender;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.media.nearby.NearbyMediaDevicesManager;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
@@ -89,6 +90,7 @@ public class MediaOutputBaseDialogTest extends SysuiTestCase {
private final AudioManager mAudioManager = mock(AudioManager.class);
private PowerExemptionManager mPowerExemptionManager = mock(PowerExemptionManager.class);
private KeyguardManager mKeyguardManager = mock(KeyguardManager.class);
+ private FeatureFlags mFlags = mock(FeatureFlags.class);
private List<MediaController> mMediaControllers = new ArrayList<>();
private MediaOutputBaseDialogImpl mMediaOutputBaseDialogImpl;
@@ -121,7 +123,7 @@ public class MediaOutputBaseDialogTest extends SysuiTestCase {
mMediaSessionManager, mLocalBluetoothManager, mStarter,
mNotifCollection, mDialogLaunchAnimator,
Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
- mKeyguardManager);
+ mKeyguardManager, mFlags);
mMediaOutputBaseDialogImpl = new MediaOutputBaseDialogImpl(mContext, mBroadcastSender,
mMediaOutputController);
mMediaOutputBaseDialogImpl.onCreate(new Bundle());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
index cb31fde26bf2..c544c0e02d34 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
@@ -62,6 +62,7 @@ import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.animation.DialogLaunchAnimator;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.media.nearby.NearbyMediaDevicesManager;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -110,6 +111,7 @@ public class MediaOutputControllerTest extends SysuiTestCase {
private PowerExemptionManager mPowerExemptionManager = mock(PowerExemptionManager.class);
private CommonNotifCollection mNotifCollection = mock(CommonNotifCollection.class);
private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class);
+ private FeatureFlags mFlags = mock(FeatureFlags.class);
private final ActivityLaunchAnimator.Controller mActivityLaunchAnimatorController = mock(
ActivityLaunchAnimator.Controller.class);
private final NearbyMediaDevicesManager mNearbyMediaDevicesManager = mock(
@@ -141,7 +143,7 @@ public class MediaOutputControllerTest extends SysuiTestCase {
mMediaSessionManager, mLocalBluetoothManager, mStarter,
mNotifCollection, mDialogLaunchAnimator,
Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
- mKeyguardManager);
+ mKeyguardManager, mFlags);
mLocalMediaManager = spy(mMediaOutputController.mLocalMediaManager);
mMediaOutputController.mLocalMediaManager = mLocalMediaManager;
MediaDescription.Builder builder = new MediaDescription.Builder();
@@ -194,7 +196,7 @@ public class MediaOutputControllerTest extends SysuiTestCase {
mMediaSessionManager, mLocalBluetoothManager, mStarter,
mNotifCollection, mDialogLaunchAnimator,
Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
- mKeyguardManager);
+ mKeyguardManager, mFlags);
mMediaOutputController.start(mCb);
@@ -224,7 +226,7 @@ public class MediaOutputControllerTest extends SysuiTestCase {
mMediaSessionManager, mLocalBluetoothManager, mStarter,
mNotifCollection, mDialogLaunchAnimator,
Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
- mKeyguardManager);
+ mKeyguardManager, mFlags);
mMediaOutputController.start(mCb);
@@ -318,7 +320,7 @@ public class MediaOutputControllerTest extends SysuiTestCase {
mMediaSessionManager, mLocalBluetoothManager, mStarter,
mNotifCollection, mDialogLaunchAnimator,
Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
- mKeyguardManager);
+ mKeyguardManager, mFlags);
testMediaOutputController.start(mCb);
reset(mCb);
@@ -341,7 +343,7 @@ public class MediaOutputControllerTest extends SysuiTestCase {
mMediaSessionManager, mLocalBluetoothManager, mStarter,
mNotifCollection, mDialogLaunchAnimator,
Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
- mKeyguardManager);
+ mKeyguardManager, mFlags);
testMediaOutputController.start(mCb);
reset(mCb);
@@ -377,7 +379,7 @@ public class MediaOutputControllerTest extends SysuiTestCase {
mMediaSessionManager, mLocalBluetoothManager, mStarter,
mNotifCollection, mDialogLaunchAnimator,
Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
- mKeyguardManager);
+ mKeyguardManager, mFlags);
LocalMediaManager testLocalMediaManager = spy(testMediaOutputController.mLocalMediaManager);
testMediaOutputController.mLocalMediaManager = testLocalMediaManager;
@@ -394,7 +396,7 @@ public class MediaOutputControllerTest extends SysuiTestCase {
mMediaSessionManager, mLocalBluetoothManager, mStarter,
mNotifCollection, mDialogLaunchAnimator,
Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
- mKeyguardManager);
+ mKeyguardManager, mFlags);
LocalMediaManager testLocalMediaManager = spy(testMediaOutputController.mLocalMediaManager);
testMediaOutputController.mLocalMediaManager = testLocalMediaManager;
@@ -671,7 +673,7 @@ public class MediaOutputControllerTest extends SysuiTestCase {
mMediaSessionManager, mLocalBluetoothManager, mStarter,
mNotifCollection, mDialogLaunchAnimator,
Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
- mKeyguardManager);
+ mKeyguardManager, mFlags);
assertThat(mMediaOutputController.getNotificationIcon()).isNull();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
index bae3569cdc93..31866a8df7e5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
@@ -50,6 +50,7 @@ import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.broadcast.BroadcastSender;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.media.nearby.NearbyMediaDevicesManager;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
@@ -93,6 +94,7 @@ public class MediaOutputDialogTest extends SysuiTestCase {
private final AudioManager mAudioManager = mock(AudioManager.class);
private PowerExemptionManager mPowerExemptionManager = mock(PowerExemptionManager.class);
private KeyguardManager mKeyguardManager = mock(KeyguardManager.class);
+ private FeatureFlags mFlags = mock(FeatureFlags.class);
private List<MediaController> mMediaControllers = new ArrayList<>();
private MediaOutputDialog mMediaOutputDialog;
@@ -115,7 +117,7 @@ public class MediaOutputDialogTest extends SysuiTestCase {
mMediaSessionManager, mLocalBluetoothManager, mStarter,
mNotifCollection, mDialogLaunchAnimator,
Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
- mKeyguardManager);
+ mKeyguardManager, mFlags);
mMediaOutputController.mLocalMediaManager = mLocalMediaManager;
mMediaOutputDialog = new MediaOutputDialog(mContext, false, mBroadcastSender,
mMediaOutputController, mUiEventLogger);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt
index 6a4c0f60466d..cce3e369c0b8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt
@@ -65,41 +65,14 @@ class MediaTttUtilsTest : SysuiTestCase() {
}
@Test
- fun getIconFromPackageName_nullPackageName_returnsDefault() {
- val icon = MediaTttUtils.getIconFromPackageName(context, appPackageName = null, logger)
-
- val expectedDesc =
- ContentDescription.Resource(R.string.media_output_dialog_unknown_launch_app_name)
- .loadContentDescription(context)
- assertThat(icon.contentDescription.loadContentDescription(context)).isEqualTo(expectedDesc)
- }
-
- @Test
- fun getIconFromPackageName_invalidPackageName_returnsDefault() {
- val icon = MediaTttUtils.getIconFromPackageName(context, "fakePackageName", logger)
-
- val expectedDesc =
- ContentDescription.Resource(R.string.media_output_dialog_unknown_launch_app_name)
- .loadContentDescription(context)
- assertThat(icon.contentDescription.loadContentDescription(context)).isEqualTo(expectedDesc)
- }
-
- @Test
- fun getIconFromPackageName_validPackageName_returnsAppInfo() {
- val icon = MediaTttUtils.getIconFromPackageName(context, PACKAGE_NAME, logger)
-
- assertThat(icon)
- .isEqualTo(Icon.Loaded(appIconFromPackageName, ContentDescription.Loaded(APP_NAME)))
- }
-
- @Test
fun getIconInfoFromPackageName_nullPackageName_returnsDefault() {
val iconInfo =
MediaTttUtils.getIconInfoFromPackageName(context, appPackageName = null, logger)
assertThat(iconInfo.isAppIcon).isFalse()
- assertThat(iconInfo.contentDescription)
+ assertThat(iconInfo.contentDescription.loadContentDescription(context))
.isEqualTo(context.getString(R.string.media_output_dialog_unknown_launch_app_name))
+ assertThat(iconInfo.icon).isEqualTo(MediaTttIcon.Resource(R.drawable.ic_cast))
}
@Test
@@ -107,8 +80,9 @@ class MediaTttUtilsTest : SysuiTestCase() {
val iconInfo = MediaTttUtils.getIconInfoFromPackageName(context, "fakePackageName", logger)
assertThat(iconInfo.isAppIcon).isFalse()
- assertThat(iconInfo.contentDescription)
+ assertThat(iconInfo.contentDescription.loadContentDescription(context))
.isEqualTo(context.getString(R.string.media_output_dialog_unknown_launch_app_name))
+ assertThat(iconInfo.icon).isEqualTo(MediaTttIcon.Resource(R.drawable.ic_cast))
}
@Test
@@ -116,8 +90,48 @@ class MediaTttUtilsTest : SysuiTestCase() {
val iconInfo = MediaTttUtils.getIconInfoFromPackageName(context, PACKAGE_NAME, logger)
assertThat(iconInfo.isAppIcon).isTrue()
- assertThat(iconInfo.drawable).isEqualTo(appIconFromPackageName)
- assertThat(iconInfo.contentDescription).isEqualTo(APP_NAME)
+ assertThat(iconInfo.icon).isEqualTo(MediaTttIcon.Loaded(appIconFromPackageName))
+ assertThat(iconInfo.contentDescription.loadContentDescription(context)).isEqualTo(APP_NAME)
+ }
+
+ @Test
+ fun iconInfo_toTintedIcon_loaded() {
+ val contentDescription = ContentDescription.Loaded("test")
+ val drawable = context.getDrawable(R.drawable.ic_cake)!!
+ val tintAttr = android.R.attr.textColorTertiary
+
+ val iconInfo =
+ IconInfo(
+ contentDescription,
+ MediaTttIcon.Loaded(drawable),
+ tintAttr,
+ isAppIcon = false,
+ )
+
+ val tinted = iconInfo.toTintedIcon()
+
+ assertThat(tinted.icon).isEqualTo(Icon.Loaded(drawable, contentDescription))
+ assertThat(tinted.tintAttr).isEqualTo(tintAttr)
+ }
+
+ @Test
+ fun iconInfo_toTintedIcon_resource() {
+ val contentDescription = ContentDescription.Loaded("test")
+ val drawableRes = R.drawable.ic_cake
+ val tintAttr = android.R.attr.textColorTertiary
+
+ val iconInfo =
+ IconInfo(
+ contentDescription,
+ MediaTttIcon.Resource(drawableRes),
+ tintAttr,
+ isAppIcon = false
+ )
+
+ val tinted = iconInfo.toTintedIcon()
+
+ assertThat(tinted.icon).isEqualTo(Icon.Resource(drawableRes, contentDescription))
+ assertThat(tinted.tintAttr).isEqualTo(tintAttr)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
index 4437394da943..311740e17310 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
@@ -265,6 +265,8 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {
@Test
fun commandQueueCallback_transferToReceiverSucceeded_triggersCorrectChip() {
+ displayReceiverTriggered()
+ reset(vibratorHelper)
commandQueueCallback.updateMediaTapToTransferSenderDisplay(
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
routeInfo,
@@ -278,13 +280,15 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {
.isEqualTo(ChipStateSender.TRANSFER_TO_RECEIVER_SUCCEEDED.getExpectedStateText())
assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE)
assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE)
- assertThat(uiEventLoggerFake.eventId(0))
+ // Event index 1 since initially displaying the triggered chip would also log an event.
+ assertThat(uiEventLoggerFake.eventId(1))
.isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_SUCCEEDED.id)
verify(vibratorHelper, never()).vibrate(any<VibrationEffect>())
}
@Test
fun transferToReceiverSucceeded_nullUndoCallback_noUndo() {
+ displayReceiverTriggered()
commandQueueCallback.updateMediaTapToTransferSenderDisplay(
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
routeInfo,
@@ -297,6 +301,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {
@Test
fun transferToReceiverSucceeded_withUndoRunnable_undoVisible() {
+ displayReceiverTriggered()
commandQueueCallback.updateMediaTapToTransferSenderDisplay(
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
routeInfo,
@@ -313,6 +318,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {
@Test
fun transferToReceiverSucceeded_undoButtonClick_switchesToTransferToThisDeviceTriggered() {
var undoCallbackCalled = false
+ displayReceiverTriggered()
commandQueueCallback.updateMediaTapToTransferSenderDisplay(
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
routeInfo,
@@ -325,8 +331,9 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {
getChipbarView().getUndoButton().performClick()
- // Event index 1 since initially displaying the succeeded chip would also log an event
- assertThat(uiEventLoggerFake.eventId(1))
+ // Event index 2 since initially displaying the triggered and succeeded chip would also log
+ // events.
+ assertThat(uiEventLoggerFake.eventId(2))
.isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_UNDO_TRANSFER_TO_RECEIVER_CLICKED.id)
assertThat(undoCallbackCalled).isTrue()
assertThat(getChipbarView().getChipText())
@@ -335,6 +342,8 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {
@Test
fun commandQueueCallback_transferToThisDeviceSucceeded_triggersCorrectChip() {
+ displayThisDeviceTriggered()
+ reset(vibratorHelper)
commandQueueCallback.updateMediaTapToTransferSenderDisplay(
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
routeInfo,
@@ -348,13 +357,15 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {
.isEqualTo(ChipStateSender.TRANSFER_TO_THIS_DEVICE_SUCCEEDED.getExpectedStateText())
assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE)
assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE)
- assertThat(uiEventLoggerFake.eventId(0))
+ // Event index 1 since initially displaying the triggered chip would also log an event.
+ assertThat(uiEventLoggerFake.eventId(1))
.isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_SUCCEEDED.id)
verify(vibratorHelper, never()).vibrate(any<VibrationEffect>())
}
@Test
fun transferToThisDeviceSucceeded_nullUndoCallback_noUndo() {
+ displayThisDeviceTriggered()
commandQueueCallback.updateMediaTapToTransferSenderDisplay(
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
routeInfo,
@@ -367,6 +378,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {
@Test
fun transferToThisDeviceSucceeded_withUndoRunnable_undoVisible() {
+ displayThisDeviceTriggered()
commandQueueCallback.updateMediaTapToTransferSenderDisplay(
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
routeInfo,
@@ -383,6 +395,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {
@Test
fun transferToThisDeviceSucceeded_undoButtonClick_switchesToTransferToThisDeviceTriggered() {
var undoCallbackCalled = false
+ displayThisDeviceTriggered()
commandQueueCallback.updateMediaTapToTransferSenderDisplay(
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
routeInfo,
@@ -395,8 +408,9 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {
getChipbarView().getUndoButton().performClick()
- // Event index 1 since initially displaying the succeeded chip would also log an event
- assertThat(uiEventLoggerFake.eventId(1))
+ // Event index 2 since initially displaying the triggered and succeeded chip would also log
+ // events.
+ assertThat(uiEventLoggerFake.eventId(2))
.isEqualTo(
MediaTttSenderUiEvents.MEDIA_TTT_SENDER_UNDO_TRANSFER_TO_THIS_DEVICE_CLICKED.id
)
@@ -407,6 +421,8 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {
@Test
fun commandQueueCallback_transferToReceiverFailed_triggersCorrectChip() {
+ displayReceiverTriggered()
+ reset(vibratorHelper)
commandQueueCallback.updateMediaTapToTransferSenderDisplay(
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_FAILED,
routeInfo,
@@ -421,7 +437,8 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {
assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE)
assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE)
assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.VISIBLE)
- assertThat(uiEventLoggerFake.eventId(0))
+ // Event index 1 since initially displaying the triggered chip would also log an event.
+ assertThat(uiEventLoggerFake.eventId(1))
.isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_FAILED.id)
verify(vibratorHelper).vibrate(any<VibrationEffect>())
}
@@ -429,6 +446,12 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {
@Test
fun commandQueueCallback_transferToThisDeviceFailed_triggersCorrectChip() {
commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED,
+ routeInfo,
+ null
+ )
+ reset(vibratorHelper)
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_FAILED,
routeInfo,
null
@@ -442,7 +465,8 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {
assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE)
assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE)
assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.VISIBLE)
- assertThat(uiEventLoggerFake.eventId(0))
+ // Event index 1 since initially displaying the triggered chip would also log an event.
+ assertThat(uiEventLoggerFake.eventId(1))
.isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_FAILED.id)
verify(vibratorHelper).vibrate(any<VibrationEffect>())
}
@@ -517,6 +541,166 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {
}
@Test
+ fun commandQueueCallback_receiverTriggeredThenAlmostStart_invalidTransitionLogged() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED,
+ routeInfo,
+ null
+ )
+ verify(windowManager).addView(any(), any())
+ reset(windowManager)
+
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
+ routeInfo,
+ null
+ )
+
+ verify(logger).logInvalidStateTransitionError(any(), any())
+ verify(windowManager, never()).addView(any(), any())
+ }
+
+ @Test
+ fun commandQueueCallback_thisDeviceTriggeredThenAlmostEnd_invalidTransitionLogged() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED,
+ routeInfo,
+ null
+ )
+ verify(windowManager).addView(any(), any())
+ reset(windowManager)
+
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST,
+ routeInfo,
+ null
+ )
+
+ verify(logger).logInvalidStateTransitionError(any(), any())
+ verify(windowManager, never()).addView(any(), any())
+ }
+
+ @Test
+ fun commandQueueCallback_receiverSucceededThenReceiverTriggered_invalidTransitionLogged() {
+ displayReceiverTriggered()
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
+ routeInfo,
+ null
+ )
+ reset(windowManager)
+
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED,
+ routeInfo,
+ null
+ )
+
+ verify(logger).logInvalidStateTransitionError(any(), any())
+ verify(windowManager, never()).addView(any(), any())
+ }
+
+ @Test
+ fun commandQueueCallback_thisDeviceSucceededThenThisDeviceTriggered_invalidTransitionLogged() {
+ displayThisDeviceTriggered()
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
+ routeInfo,
+ null
+ )
+ reset(windowManager)
+
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED,
+ routeInfo,
+ null
+ )
+
+ verify(logger).logInvalidStateTransitionError(any(), any())
+ verify(windowManager, never()).addView(any(), any())
+ }
+
+ @Test
+ fun commandQueueCallback_almostStartThenReceiverSucceeded_invalidTransitionLogged() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
+ routeInfo,
+ null
+ )
+ verify(windowManager).addView(any(), any())
+ reset(windowManager)
+
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
+ routeInfo,
+ null
+ )
+
+ verify(logger).logInvalidStateTransitionError(any(), any())
+ verify(windowManager, never()).addView(any(), any())
+ }
+
+ @Test
+ fun commandQueueCallback_almostEndThenThisDeviceSucceeded_invalidTransitionLogged() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST,
+ routeInfo,
+ null
+ )
+ verify(windowManager).addView(any(), any())
+ reset(windowManager)
+
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
+ routeInfo,
+ null
+ )
+
+ verify(logger).logInvalidStateTransitionError(any(), any())
+ verify(windowManager, never()).addView(any(), any())
+ }
+
+ @Test
+ fun commandQueueCallback_AlmostStartThenReceiverFailed_invalidTransitionLogged() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
+ routeInfo,
+ null
+ )
+ verify(windowManager).addView(any(), any())
+ reset(windowManager)
+
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_FAILED,
+ routeInfo,
+ null
+ )
+
+ verify(logger).logInvalidStateTransitionError(any(), any())
+ verify(windowManager, never()).addView(any(), any())
+ }
+
+ @Test
+ fun commandQueueCallback_almostEndThenThisDeviceFailed_invalidTransitionLogged() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST,
+ routeInfo,
+ null
+ )
+ verify(windowManager).addView(any(), any())
+ reset(windowManager)
+
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_FAILED,
+ routeInfo,
+ null
+ )
+
+ verify(logger).logInvalidStateTransitionError(any(), any())
+ verify(windowManager, never()).addView(any(), any())
+ }
+
+ @Test
fun receivesNewStateFromCommandQueue_isLogged() {
commandQueueCallback.updateMediaTapToTransferSenderDisplay(
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
@@ -575,6 +759,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {
@Test
fun transferToReceiverSucceededThenFarFromReceiver_viewStillDisplayedButDoesTimeOut() {
+ displayReceiverTriggered()
commandQueueCallback.updateMediaTapToTransferSenderDisplay(
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
routeInfo,
@@ -598,6 +783,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {
@Test
fun transferToThisDeviceSucceededThenFarFromReceiver_viewStillDisplayedButDoesTimeOut() {
+ displayThisDeviceTriggered()
commandQueueCallback.updateMediaTapToTransferSenderDisplay(
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
routeInfo,
@@ -621,6 +807,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {
@Test
fun transferToReceiverSucceeded_thenUndo_thenFar_viewStillDisplayedButDoesTimeOut() {
+ displayReceiverTriggered()
commandQueueCallback.updateMediaTapToTransferSenderDisplay(
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
routeInfo,
@@ -660,6 +847,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {
@Test
fun transferToThisDeviceSucceeded_thenUndo_thenFar_viewStillDisplayedButDoesTimeOut() {
+ displayThisDeviceTriggered()
commandQueueCallback.updateMediaTapToTransferSenderDisplay(
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
routeInfo,
@@ -717,6 +905,26 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {
private fun ChipStateSender.getExpectedStateText(): String? {
return this.getChipTextString(context, OTHER_DEVICE_NAME).loadText(context)
}
+
+ // display receiver triggered state helper method to make sure we start from a valid state
+ // transition (FAR_FROM_RECEIVER -> TRANSFER_TO_RECEIVER_TRIGGERED).
+ private fun displayReceiverTriggered() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED,
+ routeInfo,
+ null
+ )
+ }
+
+ // display this device triggered state helper method to make sure we start from a valid state
+ // transition (FAR_FROM_RECEIVER -> TRANSFER_TO_THIS_DEVICE_TRIGGERED).
+ private fun displayThisDeviceTriggered() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED,
+ routeInfo,
+ null
+ )
+ }
}
private const val APP_NAME = "Fake app name"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
index 9758842d1e35..4a9c7508b1b3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
@@ -16,16 +16,21 @@
package com.android.systemui.notetask
import android.app.KeyguardManager
+import android.content.ComponentName
import android.content.Context
import android.content.Intent
+import android.content.pm.PackageManager
import android.os.UserManager
import android.test.suitebuilder.annotation.SmallTest
-import android.view.KeyEvent
import androidx.test.runner.AndroidJUnit4
import com.android.systemui.SysuiTestCase
import com.android.systemui.notetask.NoteTaskIntentResolver.Companion.NOTES_ACTION
+import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.whenever
import com.android.wm.shell.bubbles.Bubbles
+import com.google.common.truth.Truth.assertThat
import java.util.Optional
import org.junit.Before
import org.junit.Test
@@ -48,6 +53,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
private val notesIntent = Intent(NOTES_ACTION)
@Mock lateinit var context: Context
+ @Mock lateinit var packageManager: PackageManager
@Mock lateinit var noteTaskIntentResolver: NoteTaskIntentResolver
@Mock lateinit var bubbles: Bubbles
@Mock lateinit var optionalBubbles: Optional<Bubbles>
@@ -60,6 +66,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
fun setUp() {
MockitoAnnotations.initMocks(this)
+ whenever(context.packageManager).thenReturn(packageManager)
whenever(noteTaskIntentResolver.resolveIntent()).thenReturn(notesIntent)
whenever(optionalBubbles.orElse(null)).thenReturn(bubbles)
whenever(optionalKeyguardManager.orElse(null)).thenReturn(keyguardManager)
@@ -78,89 +85,125 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
)
}
+ // region showNoteTask
@Test
- fun handleSystemKey_keyguardIsLocked_shouldStartActivity() {
+ fun showNoteTask_keyguardIsLocked_shouldStartActivity() {
whenever(keyguardManager.isKeyguardLocked).thenReturn(true)
- createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
+ createNoteTaskController().showNoteTask(isInMultiWindowMode = false)
verify(context).startActivity(notesIntent)
verify(bubbles, never()).showAppBubble(notesIntent)
}
@Test
- fun handleSystemKey_keyguardIsUnlocked_shouldStartBubbles() {
+ fun showNoteTask_keyguardIsUnlocked_shouldStartBubbles() {
whenever(keyguardManager.isKeyguardLocked).thenReturn(false)
- createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
+ createNoteTaskController().showNoteTask(isInMultiWindowMode = false)
verify(bubbles).showAppBubble(notesIntent)
verify(context, never()).startActivity(notesIntent)
}
@Test
- fun handleSystemKey_receiveInvalidSystemKey_shouldDoNothing() {
- createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_UNKNOWN)
+ fun showNoteTask_isInMultiWindowMode_shouldStartActivity() {
+ whenever(keyguardManager.isKeyguardLocked).thenReturn(false)
- verify(context, never()).startActivity(notesIntent)
+ createNoteTaskController().showNoteTask(isInMultiWindowMode = true)
+
+ verify(context).startActivity(notesIntent)
verify(bubbles, never()).showAppBubble(notesIntent)
}
@Test
- fun handleSystemKey_bubblesIsNull_shouldDoNothing() {
+ fun showNoteTask_bubblesIsNull_shouldDoNothing() {
whenever(optionalBubbles.orElse(null)).thenReturn(null)
- createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
+ createNoteTaskController().showNoteTask(isInMultiWindowMode = false)
verify(context, never()).startActivity(notesIntent)
verify(bubbles, never()).showAppBubble(notesIntent)
}
@Test
- fun handleSystemKey_keyguardManagerIsNull_shouldDoNothing() {
+ fun showNoteTask_keyguardManagerIsNull_shouldDoNothing() {
whenever(optionalKeyguardManager.orElse(null)).thenReturn(null)
- createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
+ createNoteTaskController().showNoteTask(isInMultiWindowMode = false)
verify(context, never()).startActivity(notesIntent)
verify(bubbles, never()).showAppBubble(notesIntent)
}
@Test
- fun handleSystemKey_userManagerIsNull_shouldDoNothing() {
+ fun showNoteTask_userManagerIsNull_shouldDoNothing() {
whenever(optionalUserManager.orElse(null)).thenReturn(null)
- createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
+ createNoteTaskController().showNoteTask(isInMultiWindowMode = false)
verify(context, never()).startActivity(notesIntent)
verify(bubbles, never()).showAppBubble(notesIntent)
}
@Test
- fun handleSystemKey_intentResolverReturnsNull_shouldDoNothing() {
+ fun showNoteTask_intentResolverReturnsNull_shouldDoNothing() {
whenever(noteTaskIntentResolver.resolveIntent()).thenReturn(null)
- createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
+ createNoteTaskController().showNoteTask(isInMultiWindowMode = false)
verify(context, never()).startActivity(notesIntent)
verify(bubbles, never()).showAppBubble(notesIntent)
}
@Test
- fun handleSystemKey_flagDisabled_shouldDoNothing() {
- createNoteTaskController(isEnabled = false).handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
+ fun showNoteTask_flagDisabled_shouldDoNothing() {
+ createNoteTaskController(isEnabled = false).showNoteTask()
verify(context, never()).startActivity(notesIntent)
verify(bubbles, never()).showAppBubble(notesIntent)
}
@Test
- fun handleSystemKey_userIsLocked_shouldDoNothing() {
+ fun showNoteTask_userIsLocked_shouldDoNothing() {
whenever(userManager.isUserUnlocked).thenReturn(false)
- createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
+ createNoteTaskController().showNoteTask(isInMultiWindowMode = false)
verify(context, never()).startActivity(notesIntent)
verify(bubbles, never()).showAppBubble(notesIntent)
}
+ // endregion
+
+ // region setNoteTaskShortcutEnabled
+ @Test
+ fun setNoteTaskShortcutEnabled_setTrue() {
+ createNoteTaskController().setNoteTaskShortcutEnabled(value = true)
+
+ val argument = argumentCaptor<ComponentName>()
+ verify(context.packageManager)
+ .setComponentEnabledSetting(
+ argument.capture(),
+ eq(PackageManager.COMPONENT_ENABLED_STATE_ENABLED),
+ eq(PackageManager.DONT_KILL_APP),
+ )
+ val expected = ComponentName(context, CreateNoteTaskShortcutActivity::class.java)
+ assertThat(argument.value.flattenToString()).isEqualTo(expected.flattenToString())
+ }
+
+ @Test
+ fun setNoteTaskShortcutEnabled_setFalse() {
+ createNoteTaskController().setNoteTaskShortcutEnabled(value = false)
+
+ val argument = argumentCaptor<ComponentName>()
+ verify(context.packageManager)
+ .setComponentEnabledSetting(
+ argument.capture(),
+ eq(PackageManager.COMPONENT_ENABLED_STATE_DISABLED),
+ eq(PackageManager.DONT_KILL_APP),
+ )
+ val expected = ComponentName(context, CreateNoteTaskShortcutActivity::class.java)
+ assertThat(argument.value.flattenToString()).isEqualTo(expected.flattenToString())
+ }
+ // endregion
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
index 334089c43e27..538131a4dd73 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
@@ -16,10 +16,10 @@
package com.android.systemui.notetask
import android.test.suitebuilder.annotation.SmallTest
+import android.view.KeyEvent
import androidx.test.runner.AndroidJUnit4
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.CommandQueue
-import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.wm.shell.bubbles.Bubbles
import java.util.Optional
@@ -45,6 +45,7 @@ internal class NoteTaskInitializerTest : SysuiTestCase() {
@Mock lateinit var commandQueue: CommandQueue
@Mock lateinit var bubbles: Bubbles
@Mock lateinit var optionalBubbles: Optional<Bubbles>
+ @Mock lateinit var noteTaskController: NoteTaskController
@Before
fun setUp() {
@@ -57,12 +58,13 @@ internal class NoteTaskInitializerTest : SysuiTestCase() {
private fun createNoteTaskInitializer(isEnabled: Boolean = true): NoteTaskInitializer {
return NoteTaskInitializer(
optionalBubbles = optionalBubbles,
- lazyNoteTaskController = mock(),
+ noteTaskController = noteTaskController,
commandQueue = commandQueue,
isEnabled = isEnabled,
)
}
+ // region initializer
@Test
fun initialize_shouldAddCallbacks() {
createNoteTaskInitializer().initialize()
@@ -85,4 +87,35 @@ internal class NoteTaskInitializerTest : SysuiTestCase() {
verify(commandQueue, never()).addCallback(any())
}
+
+ @Test
+ fun initialize_flagEnabled_shouldEnableShortcut() {
+ createNoteTaskInitializer().initialize()
+
+ verify(noteTaskController).setNoteTaskShortcutEnabled(true)
+ }
+
+ @Test
+ fun initialize_flagDisabled_shouldDisableShortcut() {
+ createNoteTaskInitializer(isEnabled = false).initialize()
+
+ verify(noteTaskController).setNoteTaskShortcutEnabled(false)
+ }
+ // endregion
+
+ // region handleSystemKey
+ @Test
+ fun handleSystemKey_receiveValidSystemKey_shouldShowNoteTask() {
+ createNoteTaskInitializer().callbacks.handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
+
+ verify(noteTaskController).showNoteTask()
+ }
+
+ @Test
+ fun handleSystemKey_receiveInvalidSystemKey_shouldDoNothing() {
+ createNoteTaskInitializer().callbacks.handleSystemKey(KeyEvent.KEYCODE_UNKNOWN)
+
+ verify(noteTaskController, never()).showNoteTask()
+ }
+ // endregion
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/FgsManagerControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/FgsManagerControllerTest.java
index 7bae115d2edd..17eb6e20172e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/FgsManagerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/FgsManagerControllerTest.java
@@ -18,6 +18,7 @@ package com.android.systemui.qs;
import static android.os.PowerExemptionManager.REASON_ALLOWLISTED_PACKAGE;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
@@ -29,6 +30,9 @@ import static org.mockito.Mockito.verify;
import android.app.IActivityManager;
import android.app.IForegroundServiceObserver;
+import android.app.job.IUserVisibleJobObserver;
+import android.app.job.JobScheduler;
+import android.app.job.UserVisibleJobSummary;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -59,6 +63,7 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatchers;
+import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
@@ -79,6 +84,8 @@ public class FgsManagerControllerTest extends SysuiTestCase {
@Mock
IActivityManager mIActivityManager;
@Mock
+ JobScheduler mJobScheduler;
+ @Mock
PackageManager mPackageManager;
@Mock
UserTracker mUserTracker;
@@ -92,8 +99,10 @@ public class FgsManagerControllerTest extends SysuiTestCase {
private FgsManagerController mFmc;
private IForegroundServiceObserver mIForegroundServiceObserver;
+ private IUserVisibleJobObserver mIUserVisibleJobObserver;
private UserTracker.Callback mUserTrackerCallback;
private BroadcastReceiver mShowFgsManagerReceiver;
+ private InOrder mJobSchedulerInOrder;
private List<UserInfo> mUserProfiles;
@@ -111,6 +120,8 @@ public class FgsManagerControllerTest extends SysuiTestCase {
mUserProfiles = new ArrayList<>();
Mockito.doReturn(mUserProfiles).when(mUserTracker).getUserProfiles();
+ mJobSchedulerInOrder = Mockito.inOrder(mJobScheduler);
+
mFmc = createFgsManagerController();
}
@@ -132,6 +143,52 @@ public class FgsManagerControllerTest extends SysuiTestCase {
}
@Test
+ public void testNumPackages_jobs() throws RemoteException {
+ setUserProfiles(0);
+ setShowUserVisibleJobs(true);
+
+ UserVisibleJobSummary j1 = new UserVisibleJobSummary(0, 0, "pkg1", 0);
+ UserVisibleJobSummary j2 = new UserVisibleJobSummary(1, 0, "pkg2", 1);
+ Assert.assertEquals(0, mFmc.getNumRunningPackages());
+ mIUserVisibleJobObserver.onUserVisibleJobStateChanged(j1, true);
+ Assert.assertEquals(1, mFmc.getNumRunningPackages());
+ mIUserVisibleJobObserver.onUserVisibleJobStateChanged(j2, true);
+ Assert.assertEquals(2, mFmc.getNumRunningPackages());
+ mIUserVisibleJobObserver.onUserVisibleJobStateChanged(j1, false);
+ Assert.assertEquals(1, mFmc.getNumRunningPackages());
+ mIUserVisibleJobObserver.onUserVisibleJobStateChanged(j2, false);
+ Assert.assertEquals(0, mFmc.getNumRunningPackages());
+ }
+
+ @Test
+ public void testNumPackages_FgsAndJobs() throws RemoteException {
+ setUserProfiles(0);
+ setShowUserVisibleJobs(true);
+
+ Binder b1 = new Binder();
+ Binder b2 = new Binder();
+ UserVisibleJobSummary j1 = new UserVisibleJobSummary(0, 0, "pkg1", 0);
+ UserVisibleJobSummary j3 = new UserVisibleJobSummary(1, 0, "pkg3", 1);
+ Assert.assertEquals(0, mFmc.getNumRunningPackages());
+ mIForegroundServiceObserver.onForegroundStateChanged(b1, "pkg1", 0, true);
+ Assert.assertEquals(1, mFmc.getNumRunningPackages());
+ mIForegroundServiceObserver.onForegroundStateChanged(b2, "pkg2", 0, true);
+ Assert.assertEquals(2, mFmc.getNumRunningPackages());
+ mIUserVisibleJobObserver.onUserVisibleJobStateChanged(j1, true);
+ Assert.assertEquals(2, mFmc.getNumRunningPackages());
+ mIUserVisibleJobObserver.onUserVisibleJobStateChanged(j3, true);
+ Assert.assertEquals(3, mFmc.getNumRunningPackages());
+ mIForegroundServiceObserver.onForegroundStateChanged(b2, "pkg2", 0, false);
+ Assert.assertEquals(2, mFmc.getNumRunningPackages());
+ mIUserVisibleJobObserver.onUserVisibleJobStateChanged(j3, false);
+ Assert.assertEquals(1, mFmc.getNumRunningPackages());
+ mIForegroundServiceObserver.onForegroundStateChanged(b1, "pkg1", 0, false);
+ Assert.assertEquals(1, mFmc.getNumRunningPackages());
+ mIUserVisibleJobObserver.onUserVisibleJobStateChanged(j1, false);
+ Assert.assertEquals(0, mFmc.getNumRunningPackages());
+ }
+
+ @Test
public void testNumPackagesDoesNotChangeWhenSecondFgsIsStarted() throws RemoteException {
setUserProfiles(0);
@@ -243,6 +300,91 @@ public class FgsManagerControllerTest extends SysuiTestCase {
Assert.assertEquals(0, mFmc.visibleButtonsCount());
}
+ @Test
+ public void testShowUserVisibleJobsOnCreation() {
+ // Test when the default is on.
+ mDeviceConfigProxyFake.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.TASK_MANAGER_SHOW_USER_VISIBLE_JOBS,
+ "true", false);
+ FgsManagerController fmc = new FgsManagerControllerImpl(
+ mContext,
+ mMainExecutor,
+ mBackgroundExecutor,
+ mSystemClock,
+ mIActivityManager,
+ mJobScheduler,
+ mPackageManager,
+ mUserTracker,
+ mDeviceConfigProxyFake,
+ mDialogLaunchAnimator,
+ mBroadcastDispatcher,
+ mDumpManager
+ );
+ fmc.init();
+ Assert.assertTrue(fmc.getIncludesUserVisibleJobs());
+ ArgumentCaptor<IUserVisibleJobObserver> iUserVisibleJobObserverArgumentCaptor =
+ ArgumentCaptor.forClass(IUserVisibleJobObserver.class);
+ mJobSchedulerInOrder.verify(mJobScheduler)
+ .registerUserVisibleJobObserver(iUserVisibleJobObserverArgumentCaptor.capture());
+ Assert.assertNotNull(iUserVisibleJobObserverArgumentCaptor.getValue());
+
+ // Test when the default is off.
+ mDeviceConfigProxyFake.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.TASK_MANAGER_SHOW_USER_VISIBLE_JOBS,
+ "false", false);
+ fmc = new FgsManagerControllerImpl(
+ mContext,
+ mMainExecutor,
+ mBackgroundExecutor,
+ mSystemClock,
+ mIActivityManager,
+ mJobScheduler,
+ mPackageManager,
+ mUserTracker,
+ mDeviceConfigProxyFake,
+ mDialogLaunchAnimator,
+ mBroadcastDispatcher,
+ mDumpManager
+ );
+ fmc.init();
+ Assert.assertFalse(fmc.getIncludesUserVisibleJobs());
+ mJobSchedulerInOrder.verify(mJobScheduler, never()).registerUserVisibleJobObserver(any());
+ }
+
+ @Test
+ public void testShowUserVisibleJobsToggling() throws Exception {
+ setUserProfiles(0);
+ setShowUserVisibleJobs(true);
+
+ // pkg1 has only job
+ // pkg2 has both job and fgs
+ // pkg3 has only fgs
+ UserVisibleJobSummary j1 = new UserVisibleJobSummary(0, 0, "pkg1", 0);
+ UserVisibleJobSummary j2 = new UserVisibleJobSummary(1, 0, "pkg2", 1);
+ Binder b2 = new Binder();
+ Binder b3 = new Binder();
+
+ Assert.assertEquals(0, mFmc.getNumRunningPackages());
+ mIForegroundServiceObserver.onForegroundStateChanged(b2, "pkg2", 0, true);
+ mIForegroundServiceObserver.onForegroundStateChanged(b3, "pkg3", 0, true);
+ Assert.assertEquals(2, mFmc.getNumRunningPackages());
+ mIUserVisibleJobObserver.onUserVisibleJobStateChanged(j1, true);
+ mIUserVisibleJobObserver.onUserVisibleJobStateChanged(j2, true);
+ Assert.assertEquals(3, mFmc.getNumRunningPackages());
+
+ // Turn off the flag, confirm the number of packages is updated properly.
+ setShowUserVisibleJobs(false);
+ // Only pkg1 should be removed since the other two have fgs
+ Assert.assertEquals(2, mFmc.getNumRunningPackages());
+
+ setShowUserVisibleJobs(true);
+
+ Assert.assertEquals(2, mFmc.getNumRunningPackages());
+ mIUserVisibleJobObserver.onUserVisibleJobStateChanged(j1, true);
+ mIUserVisibleJobObserver.onUserVisibleJobStateChanged(j2, true);
+ Assert.assertEquals(3, mFmc.getNumRunningPackages());
+ }
+
private void setShowStopButtonForUserAllowlistedApps(boolean enable) {
mDeviceConfigProxyFake.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
SystemUiDeviceConfigFlags.TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS,
@@ -251,6 +393,33 @@ public class FgsManagerControllerTest extends SysuiTestCase {
mBackgroundExecutor.runAllReady();
}
+ private void setShowUserVisibleJobs(boolean enable) {
+ if (mFmc.getIncludesUserVisibleJobs() == enable) {
+ // No change.
+ return;
+ }
+
+ mDeviceConfigProxyFake.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.TASK_MANAGER_SHOW_USER_VISIBLE_JOBS,
+ enable ? "true" : "false", false);
+ mBackgroundExecutor.advanceClockToLast();
+ mBackgroundExecutor.runAllReady();
+
+ ArgumentCaptor<IUserVisibleJobObserver> iUserVisibleJobObserverArgumentCaptor =
+ ArgumentCaptor.forClass(IUserVisibleJobObserver.class);
+ if (enable) {
+ mJobSchedulerInOrder.verify(mJobScheduler).registerUserVisibleJobObserver(
+ iUserVisibleJobObserverArgumentCaptor.capture()
+ );
+ mIUserVisibleJobObserver = iUserVisibleJobObserverArgumentCaptor.getValue();
+ } else {
+ mJobSchedulerInOrder.verify(mJobScheduler).unregisterUserVisibleJobObserver(
+ eq(mIUserVisibleJobObserver)
+ );
+ mIUserVisibleJobObserver = null;
+ }
+ }
+
private void setBackgroundRestrictionExemptionReason(String pkgName, int uid, int reason)
throws Exception {
Mockito.doReturn(uid)
@@ -275,6 +444,7 @@ public class FgsManagerControllerTest extends SysuiTestCase {
mBackgroundExecutor,
mSystemClock,
mIActivityManager,
+ mJobScheduler,
mPackageManager,
mUserTracker,
mDeviceConfigProxyFake,
@@ -304,6 +474,15 @@ public class FgsManagerControllerTest extends SysuiTestCase {
mUserTrackerCallback = userTrackerCallbackArgumentCaptor.getValue();
mShowFgsManagerReceiver = showFgsManagerReceiverArgumentCaptor.getValue();
+ if (result.getIncludesUserVisibleJobs()) {
+ ArgumentCaptor<IUserVisibleJobObserver> iUserVisibleJobObserverArgumentCaptor =
+ ArgumentCaptor.forClass(IUserVisibleJobObserver.class);
+ mJobSchedulerInOrder.verify(mJobScheduler).registerUserVisibleJobObserver(
+ iUserVisibleJobObserverArgumentCaptor.capture()
+ );
+ mIUserVisibleJobObserver = iUserVisibleJobObserverArgumentCaptor.getValue();
+ }
+
return result;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt
deleted file mode 100644
index 2ba8782c6d02..000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt
+++ /dev/null
@@ -1,440 +0,0 @@
-package com.android.systemui.qs
-
-import android.content.Intent
-import android.os.Handler
-import android.os.UserManager
-import android.provider.Settings
-import android.testing.AndroidTestingRunner
-import android.testing.TestableLooper
-import android.testing.ViewUtils
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import androidx.test.filters.SmallTest
-import com.android.internal.logging.MetricsLogger
-import com.android.internal.logging.UiEventLogger
-import com.android.internal.logging.testing.FakeMetricsLogger
-import com.android.systemui.R
-import com.android.systemui.animation.ActivityLaunchAnimator
-import com.android.systemui.classifier.FalsingManagerFake
-import com.android.systemui.globalactions.GlobalActionsDialogLite
-import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.settings.UserTracker
-import com.android.systemui.statusbar.phone.MultiUserSwitchController
-import com.android.systemui.statusbar.policy.DeviceProvisionedController
-import com.android.systemui.statusbar.policy.FakeConfigurationController
-import com.android.systemui.statusbar.policy.UserInfoController
-import com.android.systemui.util.mockito.capture
-import com.android.systemui.util.settings.FakeSettings
-import com.android.systemui.utils.leaks.LeakCheckedTest
-import com.google.common.truth.Expect
-import com.google.common.truth.Truth.assertThat
-import javax.inject.Provider
-import org.junit.After
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentCaptor
-import org.mockito.ArgumentMatchers.any
-import org.mockito.ArgumentMatchers.anyBoolean
-import org.mockito.Captor
-import org.mockito.Mock
-import org.mockito.Mockito
-import org.mockito.Mockito.atLeastOnce
-import org.mockito.Mockito.clearInvocations
-import org.mockito.Mockito.never
-import org.mockito.Mockito.reset
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@TestableLooper.RunWithLooper(setAsMainLooper = true)
-@RunWith(AndroidTestingRunner::class)
-class FooterActionsControllerTest : LeakCheckedTest() {
-
- @get:Rule var expect: Expect = Expect.create()
-
- @Mock private lateinit var userManager: UserManager
- @Mock private lateinit var userTracker: UserTracker
- @Mock private lateinit var activityStarter: ActivityStarter
- @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
- @Mock private lateinit var userInfoController: UserInfoController
- @Mock private lateinit var multiUserSwitchControllerFactory: MultiUserSwitchController.Factory
- @Mock private lateinit var multiUserSwitchController: MultiUserSwitchController
- @Mock private lateinit var globalActionsDialogProvider: Provider<GlobalActionsDialogLite>
- @Mock private lateinit var globalActionsDialog: GlobalActionsDialogLite
- @Mock private lateinit var uiEventLogger: UiEventLogger
- @Mock private lateinit var securityFooterController: QSSecurityFooter
- @Mock private lateinit var fgsManagerController: QSFgsManagerFooter
- @Captor
- private lateinit var visibilityChangedCaptor:
- ArgumentCaptor<VisibilityChangedDispatcher.OnVisibilityChangedListener>
-
- private lateinit var controller: FooterActionsController
-
- private val configurationController = FakeConfigurationController()
- private val metricsLogger: MetricsLogger = FakeMetricsLogger()
- private val falsingManager: FalsingManagerFake = FalsingManagerFake()
- private lateinit var view: FooterActionsView
- private lateinit var testableLooper: TestableLooper
- private lateinit var fakeSettings: FakeSettings
- private lateinit var securityFooter: View
- private lateinit var fgsFooter: View
-
- @Before
- fun setUp() {
- // We want to make sure testable resources are always used
- context.ensureTestableResources()
-
- MockitoAnnotations.initMocks(this)
- testableLooper = TestableLooper.get(this)
- fakeSettings = FakeSettings()
-
- whenever(multiUserSwitchControllerFactory.create(any()))
- .thenReturn(multiUserSwitchController)
- whenever(globalActionsDialogProvider.get()).thenReturn(globalActionsDialog)
-
- securityFooter = View(mContext)
- fgsFooter = View(mContext)
-
- whenever(securityFooterController.view).thenReturn(securityFooter)
- whenever(fgsManagerController.view).thenReturn(fgsFooter)
-
- view = inflateView()
-
- controller = constructFooterActionsController(view)
- controller.init()
- ViewUtils.attachView(view)
- // View looper is the testable looper associated with the test
- testableLooper.processAllMessages()
- }
-
- @After
- fun tearDown() {
- if (view.isAttachedToWindow) {
- ViewUtils.detachView(view)
- }
- }
-
- @Test
- fun testInitializesControllers() {
- verify(multiUserSwitchController).init()
- verify(fgsManagerController).init()
- verify(securityFooterController).init()
- }
-
- @Test
- fun testLogPowerMenuClick() {
- controller.visible = true
- falsingManager.setFalseTap(false)
-
- view.findViewById<View>(R.id.pm_lite).performClick()
- // Verify clicks are logged
- verify(uiEventLogger, Mockito.times(1))
- .log(GlobalActionsDialogLite.GlobalActionsEvent.GA_OPEN_QS)
- }
-
- @Test
- fun testSettings() {
- val captor = ArgumentCaptor.forClass(Intent::class.java)
- whenever(deviceProvisionedController.isCurrentUserSetup).thenReturn(true)
- view.findViewById<View>(R.id.settings_button_container).performClick()
-
- verify(activityStarter)
- .startActivity(capture(captor), anyBoolean(), any<ActivityLaunchAnimator.Controller>())
-
- assertThat(captor.value.action).isEqualTo(Settings.ACTION_SETTINGS)
- }
-
- @Test
- fun testSettings_UserNotSetup() {
- whenever(deviceProvisionedController.isCurrentUserSetup).thenReturn(false)
- view.findViewById<View>(R.id.settings_button_container).performClick()
- // Verify Settings wasn't launched.
- verify(activityStarter, never())
- .startActivity(any(), anyBoolean(), any<ActivityLaunchAnimator.Controller>())
- }
-
- @Test
- fun testMultiUserSwitchUpdatedWhenExpansionStarts() {
- // When expansion starts, listening is set to true
- val multiUserSwitch = view.requireViewById<View>(R.id.multi_user_switch)
-
- assertThat(multiUserSwitch.visibility).isNotEqualTo(View.VISIBLE)
-
- whenever(multiUserSwitchController.isMultiUserEnabled).thenReturn(true)
-
- controller.setListening(true)
- testableLooper.processAllMessages()
-
- assertThat(multiUserSwitch.visibility).isEqualTo(View.VISIBLE)
- }
-
- @Test
- fun testMultiUserSwitchUpdatedWhenSettingChanged() {
- // Always listening to setting while View is attached
- testableLooper.processAllMessages()
-
- val multiUserSwitch = view.requireViewById<View>(R.id.multi_user_switch)
- assertThat(multiUserSwitch.visibility).isNotEqualTo(View.VISIBLE)
-
- // The setting is only used as an indicator for whether the view should refresh. The actual
- // value of the setting is ignored; isMultiUserEnabled is the source of truth
- whenever(multiUserSwitchController.isMultiUserEnabled).thenReturn(true)
-
- // Changing the value of USER_SWITCHER_ENABLED should cause the view to update
- fakeSettings.putIntForUser(Settings.Global.USER_SWITCHER_ENABLED, 1, userTracker.userId)
- testableLooper.processAllMessages()
-
- assertThat(multiUserSwitch.visibility).isEqualTo(View.VISIBLE)
- }
-
- @Test
- fun testMultiUserSettingNotListenedAfterDetach() {
- testableLooper.processAllMessages()
-
- val multiUserSwitch = view.requireViewById<View>(R.id.multi_user_switch)
- assertThat(multiUserSwitch.visibility).isNotEqualTo(View.VISIBLE)
-
- ViewUtils.detachView(view)
-
- // The setting is only used as an indicator for whether the view should refresh. The actual
- // value of the setting is ignored; isMultiUserEnabled is the source of truth
- whenever(multiUserSwitchController.isMultiUserEnabled).thenReturn(true)
-
- // Changing the value of USER_SWITCHER_ENABLED should cause the view to update
- fakeSettings.putIntForUser(Settings.Global.USER_SWITCHER_ENABLED, 1, userTracker.userId)
- testableLooper.processAllMessages()
-
- assertThat(multiUserSwitch.visibility).isNotEqualTo(View.VISIBLE)
- }
-
- @Test
- fun testCleanUpGAD() {
- reset(globalActionsDialogProvider)
- // We are creating a new controller, so detach the views from it
- (securityFooter.parent as ViewGroup).removeView(securityFooter)
- (fgsFooter.parent as ViewGroup).removeView(fgsFooter)
-
- whenever(globalActionsDialogProvider.get()).thenReturn(globalActionsDialog)
- val view = inflateView()
- controller = constructFooterActionsController(view)
- controller.init()
- verify(globalActionsDialogProvider, never()).get()
-
- // GAD is constructed during attachment
- ViewUtils.attachView(view)
- testableLooper.processAllMessages()
- verify(globalActionsDialogProvider).get()
-
- ViewUtils.detachView(view)
- testableLooper.processAllMessages()
- verify(globalActionsDialog).destroy()
- }
-
- @Test
- fun testSeparatorVisibility_noneVisible_gone() {
- verify(securityFooterController)
- .setOnVisibilityChangedListener(capture(visibilityChangedCaptor))
- val listener = visibilityChangedCaptor.value
- val separator = controller.securityFootersSeparator
-
- setVisibilities(securityFooterVisible = false, fgsFooterVisible = false, listener)
- assertThat(separator.visibility).isEqualTo(View.GONE)
- }
-
- @Test
- fun testSeparatorVisibility_onlySecurityFooterVisible_gone() {
- verify(securityFooterController)
- .setOnVisibilityChangedListener(capture(visibilityChangedCaptor))
- val listener = visibilityChangedCaptor.value
- val separator = controller.securityFootersSeparator
-
- setVisibilities(securityFooterVisible = true, fgsFooterVisible = false, listener)
- assertThat(separator.visibility).isEqualTo(View.GONE)
- }
-
- @Test
- fun testSeparatorVisibility_onlyFgsFooterVisible_gone() {
- verify(securityFooterController)
- .setOnVisibilityChangedListener(capture(visibilityChangedCaptor))
- val listener = visibilityChangedCaptor.value
- val separator = controller.securityFootersSeparator
-
- setVisibilities(securityFooterVisible = false, fgsFooterVisible = true, listener)
- assertThat(separator.visibility).isEqualTo(View.GONE)
- }
-
- @Test
- fun testSeparatorVisibility_bothVisible_visible() {
- verify(securityFooterController)
- .setOnVisibilityChangedListener(capture(visibilityChangedCaptor))
- val listener = visibilityChangedCaptor.value
- val separator = controller.securityFootersSeparator
-
- setVisibilities(securityFooterVisible = true, fgsFooterVisible = true, listener)
- assertThat(separator.visibility).isEqualTo(View.VISIBLE)
- }
-
- @Test
- fun testFgsFooterCollapsed() {
- verify(securityFooterController)
- .setOnVisibilityChangedListener(capture(visibilityChangedCaptor))
- val listener = visibilityChangedCaptor.value
-
- val booleanCaptor = ArgumentCaptor.forClass(Boolean::class.java)
-
- clearInvocations(fgsManagerController)
- setVisibilities(securityFooterVisible = false, fgsFooterVisible = true, listener)
- verify(fgsManagerController, atLeastOnce()).setCollapsed(capture(booleanCaptor))
- assertThat(booleanCaptor.allValues.last()).isFalse()
-
- clearInvocations(fgsManagerController)
- setVisibilities(securityFooterVisible = true, fgsFooterVisible = true, listener)
- verify(fgsManagerController, atLeastOnce()).setCollapsed(capture(booleanCaptor))
- assertThat(booleanCaptor.allValues.last()).isTrue()
- }
-
- @Test
- fun setExpansion_inSplitShade_alphaFollowsExpansion() {
- enableSplitShade()
-
- controller.setExpansion(0f)
- expect.that(view.alpha).isEqualTo(0f)
-
- controller.setExpansion(0.25f)
- expect.that(view.alpha).isEqualTo(0.25f)
-
- controller.setExpansion(0.5f)
- expect.that(view.alpha).isEqualTo(0.5f)
-
- controller.setExpansion(0.75f)
- expect.that(view.alpha).isEqualTo(0.75f)
-
- controller.setExpansion(1f)
- expect.that(view.alpha).isEqualTo(1f)
- }
-
- @Test
- fun setExpansion_inSplitShade_backgroundAlphaFollowsExpansion_with_0_9_delay() {
- enableSplitShade()
-
- controller.setExpansion(0f)
- expect.that(view.backgroundAlphaFraction).isEqualTo(0f)
-
- controller.setExpansion(0.5f)
- expect.that(view.backgroundAlphaFraction).isEqualTo(0f)
-
- controller.setExpansion(0.9f)
- expect.that(view.backgroundAlphaFraction).isEqualTo(0f)
-
- controller.setExpansion(0.91f)
- expect.that(view.backgroundAlphaFraction).isWithin(FLOAT_TOLERANCE).of(0.1f)
-
- controller.setExpansion(0.95f)
- expect.that(view.backgroundAlphaFraction).isWithin(FLOAT_TOLERANCE).of(0.5f)
-
- controller.setExpansion(1f)
- expect.that(view.backgroundAlphaFraction).isEqualTo(1f)
- }
-
- @Test
- fun setExpansion_inSingleShade_alphaFollowsExpansion_with_0_9_delay() {
- disableSplitShade()
-
- controller.setExpansion(0f)
- expect.that(view.alpha).isEqualTo(0f)
-
- controller.setExpansion(0.5f)
- expect.that(view.alpha).isEqualTo(0f)
-
- controller.setExpansion(0.9f)
- expect.that(view.alpha).isEqualTo(0f)
-
- controller.setExpansion(0.91f)
- expect.that(view.alpha).isWithin(FLOAT_TOLERANCE).of(0.1f)
-
- controller.setExpansion(0.95f)
- expect.that(view.alpha).isWithin(FLOAT_TOLERANCE).of(0.5f)
-
- controller.setExpansion(1f)
- expect.that(view.alpha).isEqualTo(1f)
- }
-
- @Test
- fun setExpansion_inSingleShade_backgroundAlphaAlways1() {
- disableSplitShade()
-
- controller.setExpansion(0f)
- expect.that(view.backgroundAlphaFraction).isEqualTo(1f)
-
- controller.setExpansion(0.5f)
- expect.that(view.backgroundAlphaFraction).isEqualTo(1f)
-
- controller.setExpansion(1f)
- expect.that(view.backgroundAlphaFraction).isEqualTo(1f)
- }
-
- private fun setVisibilities(
- securityFooterVisible: Boolean,
- fgsFooterVisible: Boolean,
- listener: VisibilityChangedDispatcher.OnVisibilityChangedListener
- ) {
- securityFooter.visibility = if (securityFooterVisible) View.VISIBLE else View.GONE
- listener.onVisibilityChanged(securityFooter.visibility)
- fgsFooter.visibility = if (fgsFooterVisible) View.VISIBLE else View.GONE
- listener.onVisibilityChanged(fgsFooter.visibility)
- }
-
- private fun inflateView(): FooterActionsView {
- return LayoutInflater.from(context).inflate(R.layout.footer_actions, null)
- as FooterActionsView
- }
-
- private fun constructFooterActionsController(view: FooterActionsView): FooterActionsController {
- return FooterActionsController(
- view,
- multiUserSwitchControllerFactory,
- activityStarter,
- userManager,
- userTracker,
- userInfoController,
- deviceProvisionedController,
- securityFooterController,
- fgsManagerController,
- falsingManager,
- metricsLogger,
- globalActionsDialogProvider,
- uiEventLogger,
- showPMLiteButton = true,
- fakeSettings,
- Handler(testableLooper.looper),
- configurationController)
- }
-
- private fun enableSplitShade() {
- setSplitShadeEnabled(true)
- }
-
- private fun disableSplitShade() {
- setSplitShadeEnabled(false)
- }
-
- private fun setSplitShadeEnabled(enabled: Boolean) {
- overrideResource(R.bool.config_use_split_notification_shade, enabled)
- configurationController.notifyConfigurationChanged()
- }
-}
-
-private const val FLOAT_TOLERANCE = 0.01f
-
-private val View.backgroundAlphaFraction: Float?
- get() {
- return if (background != null) {
- background.alpha / 255f
- } else {
- null
- }
- }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
index aedb9354e6db..ffe918d36d6f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
@@ -25,6 +25,7 @@ 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.ArgumentMatchers.nullable;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
@@ -32,6 +33,7 @@ import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.annotation.Nullable;
import android.app.Fragment;
import android.content.Context;
import android.graphics.Rect;
@@ -42,6 +44,7 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import androidx.lifecycle.Lifecycle;
import androidx.test.filters.SmallTest;
import com.android.keyguard.BouncerPanelExpansionCalculator;
@@ -50,12 +53,12 @@ import com.android.systemui.SysuiBaseFragmentTest;
import com.android.systemui.animation.ShadeInterpolation;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FakeFeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.media.controls.ui.MediaHost;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.qs.customize.QSCustomizerController;
import com.android.systemui.qs.dagger.QSFragmentComponent;
import com.android.systemui.qs.external.TileServiceRequestController;
+import com.android.systemui.qs.footer.ui.binder.FooterActionsViewBinder;
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.StatusBarState;
@@ -99,6 +102,8 @@ public class QSFragmentTest extends SysuiBaseFragmentTest {
@Mock private QSAnimator mQSAnimator;
@Mock private SysuiStatusBarStateController mStatusBarStateController;
@Mock private QSSquishinessController mSquishinessController;
+ @Mock private FooterActionsViewModel mFooterActionsViewModel;
+ @Mock private FooterActionsViewModel.Factory mFooterActionsViewModelFactory;
private View mQsFragmentView;
public QSFragmentTest() {
@@ -245,7 +250,8 @@ public class QSFragmentTest extends SysuiBaseFragmentTest {
fragment.setQsExpansion(expansion, panelExpansionFraction, proposedTranslation,
squishinessFraction);
- verify(mQSFooterActionController).setExpansion(panelExpansionFraction);
+ verify(mFooterActionsViewModel).onQuickSettingsExpansionChanged(
+ panelExpansionFraction, /* isInSplitShade= */ true);
}
@Test
@@ -262,7 +268,8 @@ public class QSFragmentTest extends SysuiBaseFragmentTest {
fragment.setQsExpansion(expansion, panelExpansionFraction, proposedTranslation,
squishinessFraction);
- verify(mQSFooterActionController).setExpansion(expansion);
+ verify(mFooterActionsViewModel).onQuickSettingsExpansionChanged(
+ expansion, /* isInSplitShade= */ false);
}
@Test
@@ -379,6 +386,13 @@ public class QSFragmentTest extends SysuiBaseFragmentTest {
assertThat(mQsFragmentView.getTranslationY()).isEqualTo(0);
}
+ private Lifecycle.State getListeningAndVisibilityLifecycleState() {
+ return getFragment()
+ .getListeningAndVisibilityLifecycleOwner()
+ .getLifecycle()
+ .getCurrentState();
+ }
+
@Test
public void setListeningFalse_notVisible() {
QSFragment fragment = resumeAndGetFragment();
@@ -387,7 +401,7 @@ public class QSFragmentTest extends SysuiBaseFragmentTest {
fragment.setListening(false);
verify(mQSContainerImplController).setListening(false);
- verify(mQSFooterActionController).setListening(false);
+ assertThat(getListeningAndVisibilityLifecycleState()).isEqualTo(Lifecycle.State.CREATED);
verify(mQSPanelController).setListening(eq(false), anyBoolean());
}
@@ -399,7 +413,7 @@ public class QSFragmentTest extends SysuiBaseFragmentTest {
fragment.setListening(true);
verify(mQSContainerImplController).setListening(false);
- verify(mQSFooterActionController).setListening(false);
+ assertThat(getListeningAndVisibilityLifecycleState()).isEqualTo(Lifecycle.State.STARTED);
verify(mQSPanelController).setListening(eq(false), anyBoolean());
}
@@ -411,7 +425,7 @@ public class QSFragmentTest extends SysuiBaseFragmentTest {
fragment.setListening(false);
verify(mQSContainerImplController).setListening(false);
- verify(mQSFooterActionController).setListening(false);
+ assertThat(getListeningAndVisibilityLifecycleState()).isEqualTo(Lifecycle.State.CREATED);
verify(mQSPanelController).setListening(eq(false), anyBoolean());
}
@@ -423,7 +437,7 @@ public class QSFragmentTest extends SysuiBaseFragmentTest {
fragment.setListening(true);
verify(mQSContainerImplController).setListening(true);
- verify(mQSFooterActionController).setListening(true);
+ assertThat(getListeningAndVisibilityLifecycleState()).isEqualTo(Lifecycle.State.RESUMED);
verify(mQSPanelController).setListening(eq(true), anyBoolean());
}
@@ -480,7 +494,6 @@ public class QSFragmentTest extends SysuiBaseFragmentTest {
setUpOther();
FakeFeatureFlags featureFlags = new FakeFeatureFlags();
- featureFlags.set(Flags.NEW_FOOTER_ACTIONS, false);
return new QSFragment(
new RemoteInputQuickSettingsDisabler(
context, commandQueue, mock(ConfigurationController.class)),
@@ -495,8 +508,8 @@ public class QSFragmentTest extends SysuiBaseFragmentTest {
mFalsingManager,
mock(DumpManager.class),
featureFlags,
- mock(NewFooterActionsController.class),
- mock(FooterActionsViewModel.Factory.class));
+ mock(FooterActionsController.class),
+ mFooterActionsViewModelFactory);
}
private void setUpOther() {
@@ -505,6 +518,7 @@ public class QSFragmentTest extends SysuiBaseFragmentTest {
when(mQSContainerImplController.getView()).thenReturn(mContainer);
when(mQSPanelController.getTileLayout()).thenReturn(mQQsTileLayout);
when(mQuickQSPanelController.getTileLayout()).thenReturn(mQsTileLayout);
+ when(mFooterActionsViewModelFactory.create(any())).thenReturn(mFooterActionsViewModel);
}
private void setUpMedia() {
@@ -519,15 +533,40 @@ public class QSFragmentTest extends SysuiBaseFragmentTest {
.thenReturn(mQSPanelScrollView);
when(mQsFragmentView.findViewById(R.id.header)).thenReturn(mHeader);
when(mQsFragmentView.findViewById(android.R.id.edit)).thenReturn(new View(mContext));
+ when(mQsFragmentView.findViewById(R.id.qs_footer_actions)).thenAnswer(
+ invocation -> FooterActionsViewBinder.create(mContext));
}
private void setUpInflater() {
+ LayoutInflater realInflater = LayoutInflater.from(mContext);
+
when(mLayoutInflater.cloneInContext(any(Context.class))).thenReturn(mLayoutInflater);
- when(mLayoutInflater.inflate(anyInt(), any(ViewGroup.class), anyBoolean()))
- .thenReturn(mQsFragmentView);
+ when(mLayoutInflater.inflate(anyInt(), nullable(ViewGroup.class), anyBoolean()))
+ .thenAnswer((invocation) -> inflate(realInflater, (int) invocation.getArgument(0),
+ (ViewGroup) invocation.getArgument(1),
+ (boolean) invocation.getArgument(2)));
+ when(mLayoutInflater.inflate(anyInt(), nullable(ViewGroup.class)))
+ .thenAnswer((invocation) -> inflate(realInflater, (int) invocation.getArgument(0),
+ (ViewGroup) invocation.getArgument(1)));
mContext.addMockSystemService(Context.LAYOUT_INFLATER_SERVICE, mLayoutInflater);
}
+ private View inflate(LayoutInflater realInflater, int layoutRes, @Nullable ViewGroup root) {
+ return inflate(realInflater, layoutRes, root, root != null);
+ }
+
+ private View inflate(LayoutInflater realInflater, int layoutRes, @Nullable ViewGroup root,
+ boolean attachToRoot) {
+ if (layoutRes == R.layout.footer_actions
+ || layoutRes == R.layout.footer_actions_text_button
+ || layoutRes == R.layout.footer_actions_number_button
+ || layoutRes == R.layout.footer_actions_icon_button) {
+ return realInflater.inflate(layoutRes, root, attachToRoot);
+ }
+
+ return mQsFragmentView;
+ }
+
private void setupQsComponent() {
when(mQsComponentFactory.create(any(QSFragment.class))).thenReturn(mQsFragmentComponent);
when(mQsFragmentComponent.getQSPanelController()).thenReturn(mQSPanelController);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
index 5e9c1aaad309..c656d6dd1a35 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
@@ -17,23 +17,24 @@ package com.android.systemui.qs;
import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_DEFAULT;
import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_FINANCED;
+import static com.google.common.truth.Truth.assertThat;
+
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNull;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.annotation.IdRes;
import android.app.AlertDialog;
import android.app.admin.DevicePolicyManager;
-import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.DialogInterface;
-import android.content.Intent;
import android.content.pm.UserInfo;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.VectorDrawable;
@@ -42,27 +43,27 @@ import android.os.Looper;
import android.provider.Settings;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
-import android.testing.LayoutInflaterBuilder;
-import android.testing.TestableImageView;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
-import android.testing.ViewUtils;
import android.text.SpannableStringBuilder;
import android.view.LayoutInflater;
import android.view.View;
-import android.view.ViewGroup;
-import android.widget.FrameLayout;
import android.widget.TextView;
+import androidx.annotation.Nullable;
+
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.DialogLaunchAnimator;
+import com.android.systemui.animation.Expandable;
import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.common.shared.model.Icon;
import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.qs.footer.domain.model.SecurityButtonConfig;
+import com.android.systemui.security.data.model.SecurityModel;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.policy.SecurityController;
-import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -71,8 +72,6 @@ import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
-import java.util.concurrent.atomic.AtomicInteger;
-
/*
* Compile and run the whole SystemUI test suite:
runtest --path frameworks/base/packages/SystemUI/tests
@@ -96,11 +95,6 @@ public class QSSecurityFooterTest extends SysuiTestCase {
new ComponentName("TestDPC", "Test");
private static final int DEFAULT_ICON_ID = R.drawable.ic_info_outline;
- private ViewGroup mRootView;
- private ViewGroup mSecurityFooterView;
- private TextView mFooterText;
- private TestableImageView mPrimaryFooterIcon;
- private QSSecurityFooter mFooter;
private QSSecurityFooterUtils mFooterUtils;
@Mock
private SecurityController mSecurityController;
@@ -122,58 +116,53 @@ public class QSSecurityFooterTest extends SysuiTestCase {
Looper looper = mTestableLooper.getLooper();
Handler mainHandler = new Handler(looper);
when(mUserTracker.getUserInfo()).thenReturn(mock(UserInfo.class));
- mSecurityFooterView = (ViewGroup) new LayoutInflaterBuilder(mContext)
- .replace("ImageView", TestableImageView.class)
- .build().inflate(R.layout.quick_settings_security_footer, null, false);
mFooterUtils = new QSSecurityFooterUtils(getContext(),
getContext().getSystemService(DevicePolicyManager.class), mUserTracker,
mainHandler, mActivityStarter, mSecurityController, looper, mDialogLaunchAnimator);
- mFooter = new QSSecurityFooter(mSecurityFooterView, mainHandler, mSecurityController,
- looper, mBroadcastDispatcher, mFooterUtils);
- mFooterText = mSecurityFooterView.findViewById(R.id.footer_text);
- mPrimaryFooterIcon = mSecurityFooterView.findViewById(R.id.primary_footer_icon);
when(mSecurityController.getDeviceOwnerComponentOnAnyUser())
.thenReturn(DEVICE_OWNER_COMPONENT);
when(mSecurityController.getDeviceOwnerType(DEVICE_OWNER_COMPONENT))
.thenReturn(DEVICE_OWNER_TYPE_DEFAULT);
+ }
- // mSecurityFooterView must have a ViewGroup parent so that
- // DialogLaunchAnimator.Controller.fromView() does not return null.
- mRootView = new FrameLayout(mContext);
- mRootView.addView(mSecurityFooterView);
- ViewUtils.attachView(mRootView);
+ @Nullable
+ private SecurityButtonConfig getButtonConfig() {
+ SecurityModel securityModel = SecurityModel.create(mSecurityController);
+ return mFooterUtils.getButtonConfig(securityModel);
+ }
- mFooter.init();
+ private void assertIsDefaultIcon(Icon icon) {
+ assertIsIconResource(icon, DEFAULT_ICON_ID);
}
- @After
- public void tearDown() {
- ViewUtils.detachView(mRootView);
+ private void assertIsIconResource(Icon icon, @IdRes int res) {
+ assertThat(icon).isInstanceOf(Icon.Resource.class);
+ assertEquals(res, ((Icon.Resource) icon).getRes());
+ }
+
+ private void assertIsIconDrawable(Icon icon, Drawable drawable) {
+ assertThat(icon).isInstanceOf(Icon.Loaded.class);
+ assertEquals(drawable, ((Icon.Loaded) icon).getDrawable());
}
@Test
public void testUnmanaged() {
when(mSecurityController.isDeviceManaged()).thenReturn(false);
when(mSecurityController.isProfileOwnerOfOrganizationOwnedDevice()).thenReturn(false);
- mFooter.refreshState();
-
- TestableLooper.get(this).processAllMessages();
- assertEquals(View.GONE, mSecurityFooterView.getVisibility());
+ assertNull(getButtonConfig());
}
@Test
public void testManagedNoOwnerName() {
when(mSecurityController.isDeviceManaged()).thenReturn(true);
when(mSecurityController.getDeviceOwnerOrganizationName()).thenReturn(null);
- mFooter.refreshState();
- TestableLooper.get(this).processAllMessages();
+ SecurityButtonConfig buttonConfig = getButtonConfig();
+ assertNotNull(buttonConfig);
assertEquals(mContext.getString(R.string.quick_settings_disclosure_management),
- mFooterText.getText());
- assertEquals(View.VISIBLE, mSecurityFooterView.getVisibility());
- assertEquals(View.VISIBLE, mPrimaryFooterIcon.getVisibility());
- assertEquals(DEFAULT_ICON_ID, mPrimaryFooterIcon.getLastImageResource());
+ buttonConfig.getText());
+ assertIsDefaultIcon(buttonConfig.getIcon());
}
@Test
@@ -181,15 +170,13 @@ public class QSSecurityFooterTest extends SysuiTestCase {
when(mSecurityController.isDeviceManaged()).thenReturn(true);
when(mSecurityController.getDeviceOwnerOrganizationName())
.thenReturn(MANAGING_ORGANIZATION);
- mFooter.refreshState();
- TestableLooper.get(this).processAllMessages();
+ SecurityButtonConfig buttonConfig = getButtonConfig();
+ assertNotNull(buttonConfig);
assertEquals(mContext.getString(R.string.quick_settings_disclosure_named_management,
- MANAGING_ORGANIZATION),
- mFooterText.getText());
- assertEquals(View.VISIBLE, mSecurityFooterView.getVisibility());
- assertEquals(View.VISIBLE, mPrimaryFooterIcon.getVisibility());
- assertEquals(DEFAULT_ICON_ID, mPrimaryFooterIcon.getLastImageResource());
+ MANAGING_ORGANIZATION),
+ buttonConfig.getText());
+ assertIsDefaultIcon(buttonConfig.getIcon());
}
@Test
@@ -200,15 +187,13 @@ public class QSSecurityFooterTest extends SysuiTestCase {
when(mSecurityController.getDeviceOwnerType(DEVICE_OWNER_COMPONENT))
.thenReturn(DEVICE_OWNER_TYPE_FINANCED);
- mFooter.refreshState();
-
- TestableLooper.get(this).processAllMessages();
+ SecurityButtonConfig buttonConfig = getButtonConfig();
+ assertNotNull(buttonConfig);
assertEquals(mContext.getString(
- R.string.quick_settings_financed_disclosure_named_management,
- MANAGING_ORGANIZATION), mFooterText.getText());
- assertEquals(View.VISIBLE, mSecurityFooterView.getVisibility());
- assertEquals(View.VISIBLE, mPrimaryFooterIcon.getVisibility());
- assertEquals(DEFAULT_ICON_ID, mPrimaryFooterIcon.getLastImageResource());
+ R.string.quick_settings_financed_disclosure_named_management,
+ MANAGING_ORGANIZATION),
+ buttonConfig.getText());
+ assertIsDefaultIcon(buttonConfig.getIcon());
}
@Test
@@ -220,21 +205,16 @@ public class QSSecurityFooterTest extends SysuiTestCase {
when(mUserTracker.getUserInfo()).thenReturn(mockUserInfo);
Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.DEVICE_DEMO_MODE, 1);
- mFooter.refreshState();
-
- TestableLooper.get(this).processAllMessages();
- assertEquals(View.GONE, mSecurityFooterView.getVisibility());
+ assertNull(getButtonConfig());
}
@Test
public void testUntappableView_profileOwnerOfOrgOwnedDevice() {
when(mSecurityController.isProfileOwnerOfOrganizationOwnedDevice()).thenReturn(true);
- mFooter.refreshState();
-
- TestableLooper.get(this).processAllMessages();
- assertFalse(mSecurityFooterView.isClickable());
- assertEquals(View.GONE, mSecurityFooterView.findViewById(R.id.footer_icon).getVisibility());
+ SecurityButtonConfig buttonConfig = getButtonConfig();
+ assertNotNull(buttonConfig);
+ assertFalse(buttonConfig.isClickable());
}
@Test
@@ -244,12 +224,9 @@ public class QSSecurityFooterTest extends SysuiTestCase {
when(mSecurityController.isWorkProfileOn()).thenReturn(true);
when(mSecurityController.hasWorkProfile()).thenReturn(true);
- mFooter.refreshState();
-
- TestableLooper.get(this).processAllMessages();
- assertTrue(mSecurityFooterView.isClickable());
- assertEquals(View.VISIBLE,
- mSecurityFooterView.findViewById(R.id.footer_icon).getVisibility());
+ SecurityButtonConfig buttonConfig = getButtonConfig();
+ assertNotNull(buttonConfig);
+ assertTrue(buttonConfig.isClickable());
}
@Test
@@ -258,35 +235,31 @@ public class QSSecurityFooterTest extends SysuiTestCase {
when(mSecurityController.isNetworkLoggingEnabled()).thenReturn(true);
when(mSecurityController.isWorkProfileOn()).thenReturn(false);
- mFooter.refreshState();
-
- TestableLooper.get(this).processAllMessages();
- assertFalse(mSecurityFooterView.isClickable());
- assertEquals(View.GONE, mSecurityFooterView.findViewById(R.id.footer_icon).getVisibility());
+ SecurityButtonConfig buttonConfig = getButtonConfig();
+ assertNotNull(buttonConfig);
+ assertFalse(buttonConfig.isClickable());
}
@Test
public void testNetworkLoggingEnabled_deviceOwner() {
when(mSecurityController.isDeviceManaged()).thenReturn(true);
when(mSecurityController.isNetworkLoggingEnabled()).thenReturn(true);
- mFooter.refreshState();
- TestableLooper.get(this).processAllMessages();
+ SecurityButtonConfig buttonConfig = getButtonConfig();
+ assertNotNull(buttonConfig);
assertEquals(mContext.getString(R.string.quick_settings_disclosure_management_monitoring),
- mFooterText.getText());
- assertEquals(View.VISIBLE, mPrimaryFooterIcon.getVisibility());
- assertEquals(DEFAULT_ICON_ID, mPrimaryFooterIcon.getLastImageResource());
+ buttonConfig.getText());
+ assertIsDefaultIcon(buttonConfig.getIcon());
// Same situation, but with organization name set
when(mSecurityController.getDeviceOwnerOrganizationName())
.thenReturn(MANAGING_ORGANIZATION);
- mFooter.refreshState();
-
- TestableLooper.get(this).processAllMessages();
+ buttonConfig = getButtonConfig();
+ assertNotNull(buttonConfig);
assertEquals(mContext.getString(
- R.string.quick_settings_disclosure_named_management_monitoring,
- MANAGING_ORGANIZATION),
- mFooterText.getText());
+ R.string.quick_settings_disclosure_named_management_monitoring,
+ MANAGING_ORGANIZATION),
+ buttonConfig.getText());
}
@Test
@@ -294,12 +267,12 @@ public class QSSecurityFooterTest extends SysuiTestCase {
when(mSecurityController.hasWorkProfile()).thenReturn(true);
when(mSecurityController.isNetworkLoggingEnabled()).thenReturn(true);
when(mSecurityController.isWorkProfileOn()).thenReturn(true);
- mFooter.refreshState();
- TestableLooper.get(this).processAllMessages();
+ SecurityButtonConfig buttonConfig = getButtonConfig();
+ assertNotNull(buttonConfig);
assertEquals(mContext.getString(
- R.string.quick_settings_disclosure_managed_profile_network_activity),
- mFooterText.getText());
+ R.string.quick_settings_disclosure_managed_profile_network_activity),
+ buttonConfig.getText());
}
@Test
@@ -307,21 +280,19 @@ public class QSSecurityFooterTest extends SysuiTestCase {
when(mSecurityController.hasWorkProfile()).thenReturn(true);
when(mSecurityController.isNetworkLoggingEnabled()).thenReturn(true);
when(mSecurityController.isWorkProfileOn()).thenReturn(false);
- mFooter.refreshState();
- TestableLooper.get(this).processAllMessages();
- assertEquals("", mFooterText.getText());
+ assertNull(getButtonConfig());
}
@Test
public void testManagedCACertsInstalled() {
when(mSecurityController.isDeviceManaged()).thenReturn(true);
when(mSecurityController.hasCACertInCurrentUser()).thenReturn(true);
- mFooter.refreshState();
- TestableLooper.get(this).processAllMessages();
+ SecurityButtonConfig buttonConfig = getButtonConfig();
+ assertNotNull(buttonConfig);
assertEquals(mContext.getString(R.string.quick_settings_disclosure_management_monitoring),
- mFooterText.getText());
+ buttonConfig.getText());
}
@Test
@@ -329,25 +300,23 @@ public class QSSecurityFooterTest extends SysuiTestCase {
when(mSecurityController.isDeviceManaged()).thenReturn(true);
when(mSecurityController.isVpnEnabled()).thenReturn(true);
when(mSecurityController.getPrimaryVpnName()).thenReturn(VPN_PACKAGE);
- mFooter.refreshState();
- TestableLooper.get(this).processAllMessages();
+ SecurityButtonConfig buttonConfig = getButtonConfig();
+ assertNotNull(buttonConfig);
assertEquals(mContext.getString(R.string.quick_settings_disclosure_management_named_vpn,
- VPN_PACKAGE),
- mFooterText.getText());
- assertEquals(View.VISIBLE, mPrimaryFooterIcon.getVisibility());
- assertEquals(R.drawable.stat_sys_vpn_ic, mPrimaryFooterIcon.getLastImageResource());
+ VPN_PACKAGE),
+ buttonConfig.getText());
+ assertIsIconResource(buttonConfig.getIcon(), R.drawable.stat_sys_vpn_ic);
// Same situation, but with organization name set
when(mSecurityController.getDeviceOwnerOrganizationName())
.thenReturn(MANAGING_ORGANIZATION);
- mFooter.refreshState();
-
- TestableLooper.get(this).processAllMessages();
+ buttonConfig = getButtonConfig();
+ assertNotNull(buttonConfig);
assertEquals(mContext.getString(
- R.string.quick_settings_disclosure_named_management_named_vpn,
- MANAGING_ORGANIZATION, VPN_PACKAGE),
- mFooterText.getText());
+ R.string.quick_settings_disclosure_named_management_named_vpn,
+ MANAGING_ORGANIZATION, VPN_PACKAGE),
+ buttonConfig.getText());
}
@Test
@@ -356,23 +325,21 @@ public class QSSecurityFooterTest extends SysuiTestCase {
when(mSecurityController.isVpnEnabled()).thenReturn(true);
when(mSecurityController.getPrimaryVpnName()).thenReturn(VPN_PACKAGE);
when(mSecurityController.getWorkProfileVpnName()).thenReturn(VPN_PACKAGE_2);
- mFooter.refreshState();
- TestableLooper.get(this).processAllMessages();
+ SecurityButtonConfig buttonConfig = getButtonConfig();
+ assertNotNull(buttonConfig);
assertEquals(mContext.getString(R.string.quick_settings_disclosure_management_vpns),
- mFooterText.getText());
- assertEquals(View.VISIBLE, mPrimaryFooterIcon.getVisibility());
- assertEquals(R.drawable.stat_sys_vpn_ic, mPrimaryFooterIcon.getLastImageResource());
+ buttonConfig.getText());
+ assertIsIconResource(buttonConfig.getIcon(), R.drawable.stat_sys_vpn_ic);
// Same situation, but with organization name set
when(mSecurityController.getDeviceOwnerOrganizationName())
.thenReturn(MANAGING_ORGANIZATION);
- mFooter.refreshState();
-
- TestableLooper.get(this).processAllMessages();
+ buttonConfig = getButtonConfig();
+ assertNotNull(buttonConfig);
assertEquals(mContext.getString(R.string.quick_settings_disclosure_named_management_vpns,
MANAGING_ORGANIZATION),
- mFooterText.getText());
+ buttonConfig.getText());
}
@Test
@@ -381,13 +348,12 @@ public class QSSecurityFooterTest extends SysuiTestCase {
when(mSecurityController.isNetworkLoggingEnabled()).thenReturn(true);
when(mSecurityController.isVpnEnabled()).thenReturn(true);
when(mSecurityController.getPrimaryVpnName()).thenReturn("VPN Test App");
- mFooter.refreshState();
- TestableLooper.get(this).processAllMessages();
- assertEquals(View.VISIBLE, mPrimaryFooterIcon.getVisibility());
- assertEquals(R.drawable.stat_sys_vpn_ic, mPrimaryFooterIcon.getLastImageResource());
+ SecurityButtonConfig buttonConfig = getButtonConfig();
+ assertNotNull(buttonConfig);
+ assertIsIconResource(buttonConfig.getIcon(), R.drawable.stat_sys_vpn_ic);
assertEquals(mContext.getString(R.string.quick_settings_disclosure_management_monitoring),
- mFooterText.getText());
+ buttonConfig.getText());
}
@Test
@@ -395,24 +361,23 @@ public class QSSecurityFooterTest extends SysuiTestCase {
when(mSecurityController.isDeviceManaged()).thenReturn(false);
when(mSecurityController.hasCACertInWorkProfile()).thenReturn(true);
when(mSecurityController.isWorkProfileOn()).thenReturn(true);
- mFooter.refreshState();
- TestableLooper.get(this).processAllMessages();
- assertEquals(DEFAULT_ICON_ID, mPrimaryFooterIcon.getLastImageResource());
+ SecurityButtonConfig buttonConfig = getButtonConfig();
+ assertNotNull(buttonConfig);
+ assertIsDefaultIcon(buttonConfig.getIcon());
assertEquals(mContext.getString(
R.string.quick_settings_disclosure_managed_profile_monitoring),
- mFooterText.getText());
+ buttonConfig.getText());
// Same situation, but with organization name set
when(mSecurityController.getWorkProfileOrganizationName())
.thenReturn(MANAGING_ORGANIZATION);
- mFooter.refreshState();
-
- TestableLooper.get(this).processAllMessages();
+ buttonConfig = getButtonConfig();
+ assertNotNull(buttonConfig);
assertEquals(mContext.getString(
R.string.quick_settings_disclosure_named_managed_profile_monitoring,
MANAGING_ORGANIZATION),
- mFooterText.getText());
+ buttonConfig.getText());
}
@Test
@@ -420,22 +385,20 @@ public class QSSecurityFooterTest extends SysuiTestCase {
when(mSecurityController.isDeviceManaged()).thenReturn(false);
when(mSecurityController.hasCACertInWorkProfile()).thenReturn(true);
when(mSecurityController.isWorkProfileOn()).thenReturn(false);
- mFooter.refreshState();
- TestableLooper.get(this).processAllMessages();
- assertEquals("", mFooterText.getText());
+ assertNull(getButtonConfig());
}
@Test
public void testCACertsInstalled() {
when(mSecurityController.isDeviceManaged()).thenReturn(false);
when(mSecurityController.hasCACertInCurrentUser()).thenReturn(true);
- mFooter.refreshState();
- TestableLooper.get(this).processAllMessages();
- assertEquals(DEFAULT_ICON_ID, mPrimaryFooterIcon.getLastImageResource());
+ SecurityButtonConfig buttonConfig = getButtonConfig();
+ assertNotNull(buttonConfig);
+ assertIsDefaultIcon(buttonConfig.getIcon());
assertEquals(mContext.getString(R.string.quick_settings_disclosure_monitoring),
- mFooterText.getText());
+ buttonConfig.getText());
}
@Test
@@ -443,12 +406,12 @@ public class QSSecurityFooterTest extends SysuiTestCase {
when(mSecurityController.isVpnEnabled()).thenReturn(true);
when(mSecurityController.getPrimaryVpnName()).thenReturn(VPN_PACKAGE);
when(mSecurityController.getWorkProfileVpnName()).thenReturn(VPN_PACKAGE_2);
- mFooter.refreshState();
- TestableLooper.get(this).processAllMessages();
- assertEquals(R.drawable.stat_sys_vpn_ic, mPrimaryFooterIcon.getLastImageResource());
+ SecurityButtonConfig buttonConfig = getButtonConfig();
+ assertNotNull(buttonConfig);
+ assertIsIconResource(buttonConfig.getIcon(), R.drawable.stat_sys_vpn_ic);
assertEquals(mContext.getString(R.string.quick_settings_disclosure_vpns),
- mFooterText.getText());
+ buttonConfig.getText());
}
@Test
@@ -456,14 +419,14 @@ public class QSSecurityFooterTest extends SysuiTestCase {
when(mSecurityController.isVpnEnabled()).thenReturn(true);
when(mSecurityController.getWorkProfileVpnName()).thenReturn(VPN_PACKAGE_2);
when(mSecurityController.isWorkProfileOn()).thenReturn(true);
- mFooter.refreshState();
- TestableLooper.get(this).processAllMessages();
- assertEquals(R.drawable.stat_sys_vpn_ic, mPrimaryFooterIcon.getLastImageResource());
+ SecurityButtonConfig buttonConfig = getButtonConfig();
+ assertNotNull(buttonConfig);
+ assertIsIconResource(buttonConfig.getIcon(), R.drawable.stat_sys_vpn_ic);
assertEquals(mContext.getString(
R.string.quick_settings_disclosure_managed_profile_named_vpn,
VPN_PACKAGE_2),
- mFooterText.getText());
+ buttonConfig.getText());
}
@Test
@@ -471,22 +434,19 @@ public class QSSecurityFooterTest extends SysuiTestCase {
when(mSecurityController.isVpnEnabled()).thenReturn(true);
when(mSecurityController.getWorkProfileVpnName()).thenReturn(VPN_PACKAGE_2);
when(mSecurityController.isWorkProfileOn()).thenReturn(false);
- mFooter.refreshState();
- TestableLooper.get(this).processAllMessages();
- assertEquals("", mFooterText.getText());
+ assertNull(getButtonConfig());
}
@Test
public void testProfileOwnerOfOrganizationOwnedDeviceNoName() {
when(mSecurityController.isProfileOwnerOfOrganizationOwnedDevice()).thenReturn(true);
- mFooter.refreshState();
- TestableLooper.get(this).processAllMessages();
-
+ SecurityButtonConfig buttonConfig = getButtonConfig();
+ assertNotNull(buttonConfig);
assertEquals(mContext.getString(
R.string.quick_settings_disclosure_management),
- mFooterText.getText());
+ buttonConfig.getText());
}
@Test
@@ -495,35 +455,33 @@ public class QSSecurityFooterTest extends SysuiTestCase {
when(mSecurityController.getWorkProfileOrganizationName())
.thenReturn(MANAGING_ORGANIZATION);
- mFooter.refreshState();
- TestableLooper.get(this).processAllMessages();
-
+ SecurityButtonConfig buttonConfig = getButtonConfig();
+ assertNotNull(buttonConfig);
assertEquals(mContext.getString(
R.string.quick_settings_disclosure_named_management,
MANAGING_ORGANIZATION),
- mFooterText.getText());
+ buttonConfig.getText());
}
@Test
public void testVpnEnabled() {
when(mSecurityController.isVpnEnabled()).thenReturn(true);
when(mSecurityController.getPrimaryVpnName()).thenReturn(VPN_PACKAGE);
- mFooter.refreshState();
- TestableLooper.get(this).processAllMessages();
- assertEquals(R.drawable.stat_sys_vpn_ic, mPrimaryFooterIcon.getLastImageResource());
+ SecurityButtonConfig buttonConfig = getButtonConfig();
+ assertNotNull(buttonConfig);
+ assertIsIconResource(buttonConfig.getIcon(), R.drawable.stat_sys_vpn_ic);
assertEquals(mContext.getString(R.string.quick_settings_disclosure_named_vpn,
VPN_PACKAGE),
- mFooterText.getText());
+ buttonConfig.getText());
when(mSecurityController.hasWorkProfile()).thenReturn(true);
- mFooter.refreshState();
-
- TestableLooper.get(this).processAllMessages();
+ buttonConfig = getButtonConfig();
+ assertNotNull(buttonConfig);
assertEquals(mContext.getString(
R.string.quick_settings_disclosure_personal_profile_named_vpn,
VPN_PACKAGE),
- mFooterText.getText());
+ buttonConfig.getText());
}
@Test
@@ -687,45 +645,33 @@ public class QSSecurityFooterTest extends SysuiTestCase {
}
@Test
- public void testNoClickWhenGone() {
- mFooter.refreshState();
-
- TestableLooper.get(this).processAllMessages();
-
- assertFalse(mFooter.hasFooter());
- mFooter.onClick(mFooter.getView());
-
- // Proxy for dialog being created
- verify(mDialogLaunchAnimator, never()).showFromView(any(), any());
- }
-
- @Test
public void testParentalControls() {
// Make sure the security footer is visible, so that the images are updated.
when(mSecurityController.isProfileOwnerOfOrganizationOwnedDevice()).thenReturn(true);
-
when(mSecurityController.isParentalControlsEnabled()).thenReturn(true);
+ // We use the default icon when there is no admin icon.
+ when(mSecurityController.getIcon(any())).thenReturn(null);
+ SecurityButtonConfig buttonConfig = getButtonConfig();
+ assertEquals(mContext.getString(R.string.quick_settings_disclosure_parental_controls),
+ buttonConfig.getText());
+ assertIsDefaultIcon(buttonConfig.getIcon());
+
Drawable testDrawable = new VectorDrawable();
when(mSecurityController.getIcon(any())).thenReturn(testDrawable);
assertNotNull(mSecurityController.getIcon(null));
- mFooter.refreshState();
-
- TestableLooper.get(this).processAllMessages();
-
+ buttonConfig = getButtonConfig();
+ assertNotNull(buttonConfig);
assertEquals(mContext.getString(R.string.quick_settings_disclosure_parental_controls),
- mFooterText.getText());
- assertEquals(View.VISIBLE, mPrimaryFooterIcon.getVisibility());
-
- assertEquals(testDrawable, mPrimaryFooterIcon.getDrawable());
+ buttonConfig.getText());
+ assertIsIconDrawable(buttonConfig.getIcon(), testDrawable);
// Ensure the primary icon is back to default after parental controls are gone
when(mSecurityController.isParentalControlsEnabled()).thenReturn(false);
- mFooter.refreshState();
- TestableLooper.get(this).processAllMessages();
-
- assertEquals(DEFAULT_ICON_ID, mPrimaryFooterIcon.getLastImageResource());
+ buttonConfig = getButtonConfig();
+ assertNotNull(buttonConfig);
+ assertIsDefaultIcon(buttonConfig.getIcon());
}
@Test
@@ -739,16 +685,6 @@ public class QSSecurityFooterTest extends SysuiTestCase {
}
@Test
- public void testDialogUsesDialogLauncher() {
- when(mSecurityController.isDeviceManaged()).thenReturn(true);
- mFooter.onClick(mSecurityFooterView);
-
- mTestableLooper.processAllMessages();
-
- verify(mDialogLaunchAnimator).show(any(), any());
- }
-
- @Test
public void testCreateDialogViewForFinancedDevice() {
when(mSecurityController.isDeviceManaged()).thenReturn(true);
when(mSecurityController.getDeviceOwnerOrganizationName())
@@ -778,7 +714,10 @@ public class QSSecurityFooterTest extends SysuiTestCase {
when(mSecurityController.getDeviceOwnerType(DEVICE_OWNER_COMPONENT))
.thenReturn(DEVICE_OWNER_TYPE_FINANCED);
- mFooter.showDeviceMonitoringDialog();
+ Expandable expandable = mock(Expandable.class);
+ when(expandable.dialogLaunchController(any())).thenReturn(
+ mock(DialogLaunchAnimator.Controller.class));
+ mFooterUtils.showDeviceMonitoringDialog(getContext(), expandable);
ArgumentCaptor<AlertDialog> dialogCaptor = ArgumentCaptor.forClass(AlertDialog.class);
mTestableLooper.processAllMessages();
@@ -793,47 +732,6 @@ public class QSSecurityFooterTest extends SysuiTestCase {
dialog.dismiss();
}
- @Test
- public void testVisibilityListener() {
- final AtomicInteger lastVisibility = new AtomicInteger(-1);
- VisibilityChangedDispatcher.OnVisibilityChangedListener listener = lastVisibility::set;
-
- mFooter.setOnVisibilityChangedListener(listener);
-
- when(mSecurityController.isDeviceManaged()).thenReturn(true);
- mFooter.refreshState();
- mTestableLooper.processAllMessages();
- assertEquals(View.VISIBLE, lastVisibility.get());
-
- when(mSecurityController.isDeviceManaged()).thenReturn(false);
- mFooter.refreshState();
- mTestableLooper.processAllMessages();
- assertEquals(View.GONE, lastVisibility.get());
- }
-
- @Test
- public void testBroadcastShowsDialog() {
- // Setup dialog content
- when(mSecurityController.isDeviceManaged()).thenReturn(true);
- when(mSecurityController.getDeviceOwnerOrganizationName())
- .thenReturn(MANAGING_ORGANIZATION);
- when(mSecurityController.getDeviceOwnerType(DEVICE_OWNER_COMPONENT))
- .thenReturn(DEVICE_OWNER_TYPE_FINANCED);
-
- ArgumentCaptor<BroadcastReceiver> captor = ArgumentCaptor.forClass(BroadcastReceiver.class);
- verify(mBroadcastDispatcher).registerReceiverWithHandler(captor.capture(), any(), any(),
- any());
-
- // Pretend view is not attached anymore.
- mRootView.removeView(mSecurityFooterView);
- captor.getValue().onReceive(mContext,
- new Intent(DevicePolicyManager.ACTION_SHOW_DEVICE_MONITORING_DIALOG));
- mTestableLooper.processAllMessages();
-
- assertTrue(mFooterUtils.getDialog().isShowing());
- mFooterUtils.getDialog().dismiss();
- }
-
private CharSequence addLink(CharSequence description) {
final SpannableStringBuilder message = new SpannableStringBuilder();
message.append(description);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
index 47afa70fa84b..01411c9e7f04 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
@@ -385,4 +385,86 @@ class FooterActionsViewModelTest : SysuiTestCase() {
underTest.onVisibilityChangeRequested(visible = true)
assertThat(underTest.isVisible.value).isTrue()
}
+
+ @Test
+ fun alpha_inSplitShade_followsExpansion() {
+ val underTest = utils.footerActionsViewModel()
+
+ underTest.onQuickSettingsExpansionChanged(0f, isInSplitShade = true)
+ assertThat(underTest.alpha.value).isEqualTo(0f)
+
+ underTest.onQuickSettingsExpansionChanged(0.25f, isInSplitShade = true)
+ assertThat(underTest.alpha.value).isEqualTo(0.25f)
+
+ underTest.onQuickSettingsExpansionChanged(0.5f, isInSplitShade = true)
+ assertThat(underTest.alpha.value).isEqualTo(0.5f)
+
+ underTest.onQuickSettingsExpansionChanged(0.75f, isInSplitShade = true)
+ assertThat(underTest.alpha.value).isEqualTo(0.75f)
+
+ underTest.onQuickSettingsExpansionChanged(1f, isInSplitShade = true)
+ assertThat(underTest.alpha.value).isEqualTo(1f)
+ }
+
+ @Test
+ fun backgroundAlpha_inSplitShade_followsExpansion_with_0_99_delay() {
+ val underTest = utils.footerActionsViewModel()
+ val floatTolerance = 0.01f
+
+ underTest.onQuickSettingsExpansionChanged(0f, isInSplitShade = true)
+ assertThat(underTest.backgroundAlpha.value).isEqualTo(0f)
+
+ underTest.onQuickSettingsExpansionChanged(0.5f, isInSplitShade = true)
+ assertThat(underTest.backgroundAlpha.value).isEqualTo(0f)
+
+ underTest.onQuickSettingsExpansionChanged(0.9f, isInSplitShade = true)
+ assertThat(underTest.backgroundAlpha.value).isEqualTo(0f)
+
+ underTest.onQuickSettingsExpansionChanged(0.991f, isInSplitShade = true)
+ assertThat(underTest.backgroundAlpha.value).isWithin(floatTolerance).of(0.1f)
+
+ underTest.onQuickSettingsExpansionChanged(0.995f, isInSplitShade = true)
+ assertThat(underTest.backgroundAlpha.value).isWithin(floatTolerance).of(0.5f)
+
+ underTest.onQuickSettingsExpansionChanged(1f, isInSplitShade = true)
+ assertThat(underTest.backgroundAlpha.value).isEqualTo(1f)
+ }
+
+ @Test
+ fun alpha_inSingleShade_followsExpansion_with_0_9_delay() {
+ val underTest = utils.footerActionsViewModel()
+ val floatTolerance = 0.01f
+
+ underTest.onQuickSettingsExpansionChanged(0f, isInSplitShade = false)
+ assertThat(underTest.alpha.value).isEqualTo(0f)
+
+ underTest.onQuickSettingsExpansionChanged(0.5f, isInSplitShade = false)
+ assertThat(underTest.alpha.value).isEqualTo(0f)
+
+ underTest.onQuickSettingsExpansionChanged(0.9f, isInSplitShade = false)
+ assertThat(underTest.alpha.value).isEqualTo(0f)
+
+ underTest.onQuickSettingsExpansionChanged(0.91f, isInSplitShade = false)
+ assertThat(underTest.alpha.value).isWithin(floatTolerance).of(0.1f)
+
+ underTest.onQuickSettingsExpansionChanged(0.95f, isInSplitShade = false)
+ assertThat(underTest.alpha.value).isWithin(floatTolerance).of(0.5f)
+
+ underTest.onQuickSettingsExpansionChanged(1f, isInSplitShade = false)
+ assertThat(underTest.alpha.value).isEqualTo(1f)
+ }
+
+ @Test
+ fun backgroundAlpha_inSingleShade_always1() {
+ val underTest = utils.footerActionsViewModel()
+
+ underTest.onQuickSettingsExpansionChanged(0f, isInSplitShade = false)
+ assertThat(underTest.backgroundAlpha.value).isEqualTo(1f)
+
+ underTest.onQuickSettingsExpansionChanged(0.5f, isInSplitShade = false)
+ assertThat(underTest.backgroundAlpha.value).isEqualTo(1f)
+
+ underTest.onQuickSettingsExpansionChanged(1f, isInSplitShade = false)
+ assertThat(underTest.backgroundAlpha.value).isEqualTo(1f)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java
index d91baa5e7fcb..80c39cf9e4cd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java
@@ -23,6 +23,7 @@ import static org.mockito.Mockito.when;
import android.os.Handler;
+import android.service.quicksettings.Tile;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -38,6 +39,7 @@ import com.android.systemui.qs.QSTileHost;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tiles.dialog.InternetDialogFactory;
import com.android.systemui.statusbar.connectivity.AccessPointController;
+import com.android.systemui.statusbar.connectivity.IconState;
import com.android.systemui.statusbar.connectivity.NetworkController;
import org.junit.Before;
@@ -113,4 +115,24 @@ public class InternetTileTest extends SysuiTestCase {
.isNotEqualTo(mContext.getString(R.string.quick_settings_networks_available));
assertThat(mTile.getLastTileState()).isEqualTo(-1);
}
+
+ @Test
+ public void setIsAirplaneMode_APM_enabled_wifi_disabled() {
+ IconState state = new IconState(true, 0, "");
+ mTile.mSignalCallback.setIsAirplaneMode(state);
+ mTestableLooper.processAllMessages();
+ assertThat(mTile.getState().state).isEqualTo(Tile.STATE_INACTIVE);
+ assertThat(mTile.getState().secondaryLabel)
+ .isEqualTo(mContext.getString(R.string.status_bar_airplane));
+ }
+
+ @Test
+ public void setIsAirplaneMode_APM_enabled_wifi_enabled() {
+ IconState state = new IconState(false, 0, "");
+ mTile.mSignalCallback.setIsAirplaneMode(state);
+ mTestableLooper.processAllMessages();
+ assertThat(mTile.getState().state).isEqualTo(Tile.STATE_ACTIVE);
+ assertThat(mTile.getState().secondaryLabel)
+ .isNotEqualTo(mContext.getString(R.string.status_bar_airplane));
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt
index 9c36be62e46e..88651c1292c3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt
@@ -23,9 +23,11 @@ import androidx.constraintlayout.widget.ConstraintSet.START
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Expect
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -37,6 +39,9 @@ class CombinedShadeHeaderConstraintsTest : SysuiTestCase() {
private lateinit var qsConstraint: ConstraintSet
private lateinit var largeScreenConstraint: ConstraintSet
+ @get:Rule
+ val expect: Expect = Expect.create()
+
@Before
fun setUp() {
qqsConstraint = ConstraintSet().apply {
@@ -344,6 +349,32 @@ class CombinedShadeHeaderConstraintsTest : SysuiTestCase() {
}
@Test
+ fun testCheckViewsDontChangeSizeBetweenAnimationConstraints() {
+ val views = mapOf(
+ R.id.clock to "clock",
+ R.id.date to "date",
+ R.id.statusIcons to "icons",
+ R.id.privacy_container to "privacy",
+ R.id.carrier_group to "carriers",
+ R.id.batteryRemainingIcon to "battery",
+ )
+ views.forEach { (id, name) ->
+ expect.withMessage("$name changes height")
+ .that(qqsConstraint.getConstraint(id).layout.mHeight.fromConstraint())
+ .isEqualTo(qsConstraint.getConstraint(id).layout.mHeight.fromConstraint())
+ expect.withMessage("$name changes width")
+ .that(qqsConstraint.getConstraint(id).layout.mWidth.fromConstraint())
+ .isEqualTo(qsConstraint.getConstraint(id).layout.mWidth.fromConstraint())
+ }
+ }
+
+ private fun Int.fromConstraint() = when (this) {
+ -1 -> "MATCH_PARENT"
+ -2 -> "WRAP_CONTENT"
+ else -> toString()
+ }
+
+ @Test
fun testEmptyCutoutDateIconsAreConstrainedWidth() {
CombinedShadeHeadersConstraintManagerImpl.emptyCutoutConstraints()()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
index 858d0e7b94d0..1d30ad9293a0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
@@ -46,7 +46,6 @@ import com.android.systemui.qs.carrier.QSCarrierGroup
import com.android.systemui.qs.carrier.QSCarrierGroupController
import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.HEADER_TRANSITION_ID
import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.LARGE_SCREEN_HEADER_CONSTRAINT
-import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.LARGE_SCREEN_HEADER_TRANSITION_ID
import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.QQS_HEADER_CONSTRAINT
import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.QS_HEADER_CONSTRAINT
import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
@@ -77,6 +76,7 @@ import org.mockito.Mockito.anyInt
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.inOrder
import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
import org.mockito.junit.MockitoJUnit
@@ -212,20 +212,6 @@ class LargeScreenShadeHeaderControllerCombinedTest : SysuiTestCase() {
}
@Test
- fun testCorrectConstraints() {
- val captor = ArgumentCaptor.forClass(XmlResourceParser::class.java)
-
- verify(qqsConstraints).load(eq(context), capture(captor))
- assertThat(captor.value.getResId()).isEqualTo(R.xml.qqs_header)
-
- verify(qsConstraints).load(eq(context), capture(captor))
- assertThat(captor.value.getResId()).isEqualTo(R.xml.qs_header)
-
- verify(largeScreenConstraints).load(eq(context), capture(captor))
- assertThat(captor.value.getResId()).isEqualTo(R.xml.large_screen_shade_header)
- }
-
- @Test
fun testControllersCreatedAndInitialized() {
verify(variableDateViewController).init()
@@ -287,16 +273,6 @@ class LargeScreenShadeHeaderControllerCombinedTest : SysuiTestCase() {
}
@Test
- fun testLargeScreenActive_true() {
- controller.largeScreenActive = false // Make sure there's a change
- clearInvocations(view)
-
- controller.largeScreenActive = true
-
- verify(view).setTransition(LARGE_SCREEN_HEADER_TRANSITION_ID)
- }
-
- @Test
fun testLargeScreenActive_false() {
controller.largeScreenActive = true // Make sure there's a change
clearInvocations(view)
@@ -696,6 +672,25 @@ class LargeScreenShadeHeaderControllerCombinedTest : SysuiTestCase() {
verify(clock).pivotY = height.toFloat() / 2
}
+ @Test
+ fun onDensityOrFontScaleChanged_reloadConstraints() {
+ // After density or font scale change, constraints need to be reloaded to reflect new
+ // dimensions.
+ reset(qqsConstraints)
+ reset(qsConstraints)
+ reset(largeScreenConstraints)
+
+ configurationController.notifyDensityOrFontScaleChanged()
+
+ val captor = ArgumentCaptor.forClass(XmlResourceParser::class.java)
+ verify(qqsConstraints).load(eq(context), capture(captor))
+ assertThat(captor.value.getResId()).isEqualTo(R.xml.qqs_header)
+ verify(qsConstraints).load(eq(context), capture(captor))
+ assertThat(captor.value.getResId()).isEqualTo(R.xml.qs_header)
+ verify(largeScreenConstraints).load(eq(context), capture(captor))
+ assertThat(captor.value.getResId()).isEqualTo(R.xml.large_screen_shade_header)
+ }
+
private fun View.executeLayoutChange(
left: Int,
top: Int,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index b6f74f0a13ba..56a840cae267 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -43,6 +43,7 @@ import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -530,6 +531,8 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
.setHeadsUpAppearanceController(mock(HeadsUpAppearanceController.class));
verify(mNotificationStackScrollLayoutController)
.setOnEmptySpaceClickListener(mEmptySpaceClickListenerCaptor.capture());
+ verify(mKeyguardStatusViewController).displayClock(LARGE, /* animate */ true);
+ reset(mKeyguardStatusViewController);
}
@After
@@ -609,7 +612,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
@Test
public void getVerticalSpaceForLockscreenNotifications_useLockIconBottomPadding_returnsSpaceAvailable() {
- setBottomPadding(/* stackScrollLayoutBottom= */ 100,
+ setBottomPadding(/* stackScrollLayoutBottom= */ 180,
/* lockIconPadding= */ 20,
/* indicationPadding= */ 0,
/* ambientPadding= */ 0);
@@ -620,7 +623,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
@Test
public void getVerticalSpaceForLockscreenNotifications_useIndicationBottomPadding_returnsSpaceAvailable() {
- setBottomPadding(/* stackScrollLayoutBottom= */ 100,
+ setBottomPadding(/* stackScrollLayoutBottom= */ 180,
/* lockIconPadding= */ 0,
/* indicationPadding= */ 30,
/* ambientPadding= */ 0);
@@ -631,7 +634,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
@Test
public void getVerticalSpaceForLockscreenNotifications_useAmbientBottomPadding_returnsSpaceAvailable() {
- setBottomPadding(/* stackScrollLayoutBottom= */ 100,
+ setBottomPadding(/* stackScrollLayoutBottom= */ 180,
/* lockIconPadding= */ 0,
/* indicationPadding= */ 0,
/* ambientPadding= */ 40);
@@ -802,66 +805,6 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
}
@Test
- public void handleTouchEventFromStatusBar_panelsNotEnabled_returnsFalseAndNoViewEvent() {
- when(mCommandQueue.panelsEnabled()).thenReturn(false);
-
- boolean returnVal = mNotificationPanelViewController
- .getStatusBarTouchEventHandler()
- .handleTouchEvent(
- MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0));
-
- assertThat(returnVal).isFalse();
- verify(mView, never()).dispatchTouchEvent(any());
- }
-
- @Test
- public void handleTouchEventFromStatusBar_viewNotEnabled_returnsTrueAndNoViewEvent() {
- when(mCommandQueue.panelsEnabled()).thenReturn(true);
- when(mView.isEnabled()).thenReturn(false);
-
- boolean returnVal = mNotificationPanelViewController
- .getStatusBarTouchEventHandler()
- .handleTouchEvent(
- MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0));
-
- assertThat(returnVal).isTrue();
- verify(mView, never()).dispatchTouchEvent(any());
- }
-
- @Test
- public void handleTouchEventFromStatusBar_viewNotEnabledButIsMoveEvent_viewReceivesEvent() {
- when(mCommandQueue.panelsEnabled()).thenReturn(true);
- when(mView.isEnabled()).thenReturn(false);
- MotionEvent event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0);
-
- mNotificationPanelViewController.getStatusBarTouchEventHandler().handleTouchEvent(event);
-
- verify(mView).dispatchTouchEvent(event);
- }
-
- @Test
- public void handleTouchEventFromStatusBar_panelAndViewEnabled_viewReceivesEvent() {
- when(mCommandQueue.panelsEnabled()).thenReturn(true);
- when(mView.isEnabled()).thenReturn(true);
- MotionEvent event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 2f, 0);
-
- mNotificationPanelViewController.getStatusBarTouchEventHandler().handleTouchEvent(event);
-
- verify(mView).dispatchTouchEvent(event);
- }
-
- @Test
- public void handleTouchEventFromStatusBar_topEdgeTouch_viewNeverReceivesEvent() {
- when(mCommandQueue.panelsEnabled()).thenReturn(true);
- when(mView.isEnabled()).thenReturn(true);
- MotionEvent event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0);
-
- mNotificationPanelViewController.getStatusBarTouchEventHandler().handleTouchEvent(event);
-
- verify(mView, never()).dispatchTouchEvent(event);
- }
-
- @Test
public void testA11y_initializeNode() {
AccessibilityNodeInfo nodeInfo = new AccessibilityNodeInfo();
mAccessibilityDelegate.onInitializeAccessibilityNodeInfo(mView, nodeInfo);
@@ -1014,7 +957,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
}
@Test
- public void testFinishInflate_userSwitcherDisabled_doNotInflateUserSwitchView() {
+ public void testFinishInflate_userSwitcherDisabled_doNotInflateUserSwitchView_initClock() {
givenViewAttached();
when(mResources.getBoolean(
com.android.internal.R.bool.config_keyguardUserSwitcher)).thenReturn(true);
@@ -1025,6 +968,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
mNotificationPanelViewController.onFinishInflate();
verify(mUserSwitcherStubView, never()).inflate();
+ verify(mKeyguardStatusViewController, times(3)).displayClock(LARGE, /* animate */ true);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionMonitorTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionMonitorTest.java
index 17d81c8338cb..7693fee0a1c4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionMonitorTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.util.condition;
+package com.android.systemui.shared.condition;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
@@ -68,13 +68,15 @@ public class ConditionMonitorTest extends SysuiTestCase {
mConditionMonitor = new Monitor(mExecutor);
}
- public Monitor.Subscription.Builder getDefaultBuilder(Monitor.Callback callback) {
+ public Monitor.Subscription.Builder getDefaultBuilder(
+ Monitor.Callback callback) {
return new Monitor.Subscription.Builder(callback)
.addConditions(mConditions);
}
private Condition createMockCondition() {
- final Condition condition = Mockito.mock(Condition.class);
+ final Condition condition = Mockito.mock(
+ Condition.class);
when(condition.isConditionSet()).thenReturn(true);
return condition;
}
@@ -83,11 +85,14 @@ public class ConditionMonitorTest extends SysuiTestCase {
public void testOverridingCondition() {
final Condition overridingCondition = createMockCondition();
final Condition regularCondition = createMockCondition();
- final Monitor.Callback callback = Mockito.mock(Monitor.Callback.class);
+ final Monitor.Callback callback = Mockito.mock(
+ Monitor.Callback.class);
- final Monitor.Callback referenceCallback = Mockito.mock(Monitor.Callback.class);
+ final Monitor.Callback referenceCallback = Mockito.mock(
+ Monitor.Callback.class);
- final Monitor monitor = new Monitor(mExecutor);
+ final Monitor
+ monitor = new Monitor(mExecutor);
monitor.addSubscription(getDefaultBuilder(callback)
.addCondition(overridingCondition)
@@ -136,9 +141,11 @@ public class ConditionMonitorTest extends SysuiTestCase {
final Condition overridingCondition = createMockCondition();
final Condition overridingCondition2 = createMockCondition();
final Condition regularCondition = createMockCondition();
- final Monitor.Callback callback = Mockito.mock(Monitor.Callback.class);
+ final Monitor.Callback callback = Mockito.mock(
+ Monitor.Callback.class);
- final Monitor monitor = new Monitor(mExecutor);
+ final Monitor
+ monitor = new Monitor(mExecutor);
monitor.addSubscription(getDefaultBuilder(callback)
.addCondition(overridingCondition)
@@ -211,9 +218,11 @@ public class ConditionMonitorTest extends SysuiTestCase {
public void addCallback_addSecondCallback_reportWithExistingValue() {
final Monitor.Callback callback1 =
mock(Monitor.Callback.class);
- final Condition condition = mock(Condition.class);
+ final Condition condition = mock(
+ Condition.class);
when(condition.isConditionMet()).thenReturn(true);
- final Monitor monitor = new Monitor(mExecutor);
+ final Monitor
+ monitor = new Monitor(mExecutor);
monitor.addSubscription(new Monitor.Subscription.Builder(callback1)
.addCondition(condition)
.build());
@@ -229,8 +238,10 @@ public class ConditionMonitorTest extends SysuiTestCase {
@Test
public void addCallback_noConditions_reportAllConditionsMet() {
- final Monitor monitor = new Monitor(mExecutor);
- final Monitor.Callback callback = mock(Monitor.Callback.class);
+ final Monitor
+ monitor = new Monitor(mExecutor);
+ final Monitor.Callback callback = mock(
+ Monitor.Callback.class);
monitor.addSubscription(new Monitor.Subscription.Builder(callback).build());
mExecutor.runAllReady();
@@ -239,8 +250,10 @@ public class ConditionMonitorTest extends SysuiTestCase {
@Test
public void removeCallback_noFailureOnDoubleRemove() {
- final Condition condition = mock(Condition.class);
- final Monitor monitor = new Monitor(mExecutor);
+ final Condition condition = mock(
+ Condition.class);
+ final Monitor
+ monitor = new Monitor(mExecutor);
final Monitor.Callback callback =
mock(Monitor.Callback.class);
final Monitor.Subscription.Token token = monitor.addSubscription(
@@ -255,8 +268,10 @@ public class ConditionMonitorTest extends SysuiTestCase {
@Test
public void removeCallback_shouldNoLongerReceiveUpdate() {
- final Condition condition = mock(Condition.class);
- final Monitor monitor = new Monitor(mExecutor);
+ final Condition condition = mock(
+ Condition.class);
+ final Monitor
+ monitor = new Monitor(mExecutor);
final Monitor.Callback callback =
mock(Monitor.Callback.class);
final Monitor.Subscription.Token token = monitor.addSubscription(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionTest.java
index 28788647dd58..8443221e8b7a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.util.condition;
+package com.android.systemui.shared.condition;
import static com.google.common.truth.Truth.assertThat;
@@ -47,16 +47,20 @@ public class ConditionTest extends SysuiTestCase {
@Test
public void addCallback_addFirstCallback_triggerStart() {
- final Condition.Callback callback = mock(Condition.Callback.class);
+ final Condition.Callback callback = mock(
+ Condition.Callback.class);
mCondition.addCallback(callback);
verify(mCondition).start();
}
@Test
public void addCallback_addMultipleCallbacks_triggerStartOnlyOnce() {
- final Condition.Callback callback1 = mock(Condition.Callback.class);
- final Condition.Callback callback2 = mock(Condition.Callback.class);
- final Condition.Callback callback3 = mock(Condition.Callback.class);
+ final Condition.Callback callback1 = mock(
+ Condition.Callback.class);
+ final Condition.Callback callback2 = mock(
+ Condition.Callback.class);
+ final Condition.Callback callback3 = mock(
+ Condition.Callback.class);
mCondition.addCallback(callback1);
mCondition.addCallback(callback2);
@@ -67,12 +71,14 @@ public class ConditionTest extends SysuiTestCase {
@Test
public void addCallback_alreadyStarted_triggerUpdate() {
- final Condition.Callback callback1 = mock(Condition.Callback.class);
+ final Condition.Callback callback1 = mock(
+ Condition.Callback.class);
mCondition.addCallback(callback1);
mCondition.fakeUpdateCondition(true);
- final Condition.Callback callback2 = mock(Condition.Callback.class);
+ final Condition.Callback callback2 = mock(
+ Condition.Callback.class);
mCondition.addCallback(callback2);
verify(callback2).onConditionChanged(mCondition);
assertThat(mCondition.isConditionMet()).isTrue();
@@ -80,7 +86,8 @@ public class ConditionTest extends SysuiTestCase {
@Test
public void removeCallback_removeLastCallback_triggerStop() {
- final Condition.Callback callback = mock(Condition.Callback.class);
+ final Condition.Callback callback = mock(
+ Condition.Callback.class);
mCondition.addCallback(callback);
verify(mCondition, never()).stop();
@@ -92,7 +99,8 @@ public class ConditionTest extends SysuiTestCase {
public void updateCondition_falseToTrue_reportTrue() {
mCondition.fakeUpdateCondition(false);
- final Condition.Callback callback = mock(Condition.Callback.class);
+ final Condition.Callback callback = mock(
+ Condition.Callback.class);
mCondition.addCallback(callback);
mCondition.fakeUpdateCondition(true);
@@ -104,7 +112,8 @@ public class ConditionTest extends SysuiTestCase {
public void updateCondition_trueToFalse_reportFalse() {
mCondition.fakeUpdateCondition(true);
- final Condition.Callback callback = mock(Condition.Callback.class);
+ final Condition.Callback callback = mock(
+ Condition.Callback.class);
mCondition.addCallback(callback);
mCondition.fakeUpdateCondition(false);
@@ -116,7 +125,8 @@ public class ConditionTest extends SysuiTestCase {
public void updateCondition_trueToTrue_reportNothing() {
mCondition.fakeUpdateCondition(true);
- final Condition.Callback callback = mock(Condition.Callback.class);
+ final Condition.Callback callback = mock(
+ Condition.Callback.class);
mCondition.addCallback(callback);
mCondition.fakeUpdateCondition(true);
@@ -127,7 +137,8 @@ public class ConditionTest extends SysuiTestCase {
public void updateCondition_falseToFalse_reportNothing() {
mCondition.fakeUpdateCondition(false);
- final Condition.Callback callback = mock(Condition.Callback.class);
+ final Condition.Callback callback = mock(
+ Condition.Callback.class);
mCondition.addCallback(callback);
mCondition.fakeUpdateCondition(false);
@@ -149,7 +160,8 @@ public class ConditionTest extends SysuiTestCase {
final Condition combinedCondition = mCondition.or(
new FakeCondition(/* initialValue= */ false));
- final Condition.Callback callback = mock(Condition.Callback.class);
+ final Condition.Callback callback = mock(
+ Condition.Callback.class);
combinedCondition.addCallback(callback);
assertThat(combinedCondition.isConditionSet()).isTrue();
@@ -164,7 +176,8 @@ public class ConditionTest extends SysuiTestCase {
final Condition combinedCondition = mCondition.or(
new FakeCondition(/* initialValue= */ true));
- final Condition.Callback callback = mock(Condition.Callback.class);
+ final Condition.Callback callback = mock(
+ Condition.Callback.class);
combinedCondition.addCallback(callback);
assertThat(combinedCondition.isConditionSet()).isTrue();
@@ -179,7 +192,8 @@ public class ConditionTest extends SysuiTestCase {
final Condition combinedCondition = mCondition.or(
new FakeCondition(/* initialValue= */ true));
- final Condition.Callback callback = mock(Condition.Callback.class);
+ final Condition.Callback callback = mock(
+ Condition.Callback.class);
combinedCondition.addCallback(callback);
assertThat(combinedCondition.isConditionSet()).isTrue();
@@ -195,7 +209,8 @@ public class ConditionTest extends SysuiTestCase {
final Condition combinedCondition = mCondition.or(
new FakeCondition(/* initialValue= */ null));
- final Condition.Callback callback = mock(Condition.Callback.class);
+ final Condition.Callback callback = mock(
+ Condition.Callback.class);
combinedCondition.addCallback(callback);
assertThat(combinedCondition.isConditionSet()).isTrue();
@@ -211,7 +226,8 @@ public class ConditionTest extends SysuiTestCase {
final Condition combinedCondition = mCondition.or(
new FakeCondition(/* initialValue= */ null));
- final Condition.Callback callback = mock(Condition.Callback.class);
+ final Condition.Callback callback = mock(
+ Condition.Callback.class);
combinedCondition.addCallback(callback);
assertThat(combinedCondition.isConditionSet()).isFalse();
@@ -226,7 +242,8 @@ public class ConditionTest extends SysuiTestCase {
final Condition combinedCondition = mCondition.and(
new FakeCondition(/* initialValue= */ false));
- final Condition.Callback callback = mock(Condition.Callback.class);
+ final Condition.Callback callback = mock(
+ Condition.Callback.class);
combinedCondition.addCallback(callback);
assertThat(combinedCondition.isConditionSet()).isTrue();
@@ -241,7 +258,8 @@ public class ConditionTest extends SysuiTestCase {
final Condition combinedCondition = mCondition.and(
new FakeCondition(/* initialValue= */ true));
- final Condition.Callback callback = mock(Condition.Callback.class);
+ final Condition.Callback callback = mock(
+ Condition.Callback.class);
combinedCondition.addCallback(callback);
assertThat(combinedCondition.isConditionSet()).isTrue();
@@ -256,7 +274,8 @@ public class ConditionTest extends SysuiTestCase {
final Condition combinedCondition = mCondition.and(
new FakeCondition(/* initialValue= */ false));
- final Condition.Callback callback = mock(Condition.Callback.class);
+ final Condition.Callback callback = mock(
+ Condition.Callback.class);
combinedCondition.addCallback(callback);
assertThat(combinedCondition.isConditionSet()).isTrue();
@@ -272,7 +291,8 @@ public class ConditionTest extends SysuiTestCase {
final Condition combinedCondition = mCondition.and(
new FakeCondition(/* initialValue= */ null));
- final Condition.Callback callback = mock(Condition.Callback.class);
+ final Condition.Callback callback = mock(
+ Condition.Callback.class);
combinedCondition.addCallback(callback);
assertThat(combinedCondition.isConditionSet()).isFalse();
@@ -288,7 +308,8 @@ public class ConditionTest extends SysuiTestCase {
final Condition combinedCondition = mCondition.and(
new FakeCondition(/* initialValue= */ null));
- final Condition.Callback callback = mock(Condition.Callback.class);
+ final Condition.Callback callback = mock(
+ Condition.Callback.class);
combinedCondition.addCallback(callback);
assertThat(combinedCondition.isConditionSet()).isTrue();
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/condition/FakeCondition.java b/packages/SystemUI/tests/src/com/android/systemui/shared/condition/FakeCondition.java
index 07ed1102e990..55a6d39d4644 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/condition/FakeCondition.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/condition/FakeCondition.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.util.condition;
+package com.android.systemui.shared.condition;
/**
* Fake implementation of {@link Condition}, and provides a way for tests to update
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
index faf4592d26e3..5431eba8441c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
@@ -72,6 +72,7 @@ import com.android.systemui.demomode.DemoModeController;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.log.LogBuffer;
import com.android.systemui.settings.UserTracker;
+import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
@@ -245,6 +246,7 @@ public class NetworkControllerBaseTest extends SysuiTestCase {
mFakeExecutor,
mCallbackHandler,
mock(AccessPointControllerImpl.class),
+ mock(StatusBarPipelineFlags.class),
mock(DataUsageController.class),
mMockSubDefaults,
mMockProvisionController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java
index ca75a40300cb..9441d49d454a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java
@@ -49,6 +49,7 @@ import com.android.settingslib.mobile.TelephonyIcons;
import com.android.settingslib.net.DataUsageController;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.util.CarrierConfigTracker;
@@ -150,6 +151,7 @@ public class NetworkControllerDataTest extends NetworkControllerBaseTest {
mFakeExecutor,
mCallbackHandler,
mock(AccessPointControllerImpl.class),
+ mock(StatusBarPipelineFlags.class),
mock(DataUsageController.class),
mMockSubDefaults,
mock(DeviceProvisionedController.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java
index 84c242cda459..4c1f0a8a1066 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java
@@ -44,6 +44,7 @@ import com.android.settingslib.net.DataUsageController;
import com.android.systemui.R;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.util.CarrierConfigTracker;
@@ -78,6 +79,7 @@ public class NetworkControllerSignalTest extends NetworkControllerBaseTest {
mFakeExecutor,
mCallbackHandler,
mock(AccessPointControllerImpl.class),
+ mock(StatusBarPipelineFlags.class),
mock(DataUsageController.class),
mMockSubDefaults,
mMockProvisionController,
@@ -115,6 +117,7 @@ public class NetworkControllerSignalTest extends NetworkControllerBaseTest {
mFakeExecutor,
mCallbackHandler,
mock(AccessPointControllerImpl.class),
+ mock(StatusBarPipelineFlags.class),
mock(DataUsageController.class),
mMockSubDefaults,
mMockProvisionController,
@@ -150,6 +153,7 @@ public class NetworkControllerSignalTest extends NetworkControllerBaseTest {
mFakeExecutor,
mCallbackHandler,
mock(AccessPointControllerImpl.class),
+ mock(StatusBarPipelineFlags.class),
mock(DataUsageController.class),
mMockSubDefaults,
mock(DeviceProvisionedController.class),
@@ -188,6 +192,7 @@ public class NetworkControllerSignalTest extends NetworkControllerBaseTest {
mFakeExecutor,
mCallbackHandler,
mock(AccessPointControllerImpl.class),
+ mock(StatusBarPipelineFlags.class),
mock(DataUsageController.class),
mMockSubDefaults,
mock(DeviceProvisionedController.class),
@@ -274,6 +279,7 @@ public class NetworkControllerSignalTest extends NetworkControllerBaseTest {
mFakeExecutor,
mCallbackHandler,
mock(AccessPointControllerImpl.class),
+ mock(StatusBarPipelineFlags.class),
mock(DataUsageController.class),
mMockSubDefaults,
mock(DeviceProvisionedController.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/RoundableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/RoundableTest.kt
new file mode 100644
index 000000000000..89faa239c5a2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/RoundableTest.kt
@@ -0,0 +1,164 @@
+package com.android.systemui.statusbar.notification
+
+import android.view.View
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.mock
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mockito.atLeastOnce
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+
+@SmallTest
+@RunWith(JUnit4::class)
+class RoundableTest : SysuiTestCase() {
+ val targetView: View = mock()
+ val roundable = FakeRoundable(targetView)
+
+ @Test
+ fun defaultConfig_shouldNotHaveRoundedCorner() {
+ // the expected default value for the roundness is top = 0f, bottom = 0f
+ assertEquals(0f, roundable.roundableState.topRoundness)
+ assertEquals(0f, roundable.roundableState.bottomRoundness)
+ assertEquals(false, roundable.hasRoundedCorner())
+ }
+
+ @Test
+ fun applyRoundnessAndInvalidate_should_invalidate_targetView() {
+ roundable.applyRoundnessAndInvalidate()
+
+ verify(targetView, times(1)).invalidate()
+ }
+
+ @Test
+ fun requestTopRoundness_update_and_invalidate_targetView() {
+ roundable.requestTopRoundness(value = 1f, sourceType = SOURCE1)
+
+ assertEquals(1f, roundable.roundableState.topRoundness)
+ verify(targetView, times(1)).invalidate()
+ }
+
+ @Test
+ fun requestBottomRoundness_update_and_invalidate_targetView() {
+ roundable.requestBottomRoundness(value = 1f, sourceType = SOURCE1)
+
+ assertEquals(1f, roundable.roundableState.bottomRoundness)
+ verify(targetView, times(1)).invalidate()
+ }
+
+ @Test
+ fun requestRoundness_update_and_invalidate_targetView() {
+ roundable.requestRoundness(top = 1f, bottom = 1f, sourceType = SOURCE1)
+
+ assertEquals(1f, roundable.roundableState.topRoundness)
+ assertEquals(1f, roundable.roundableState.bottomRoundness)
+ verify(targetView, atLeastOnce()).invalidate()
+ }
+
+ @Test
+ fun requestRoundnessReset_update_and_invalidate_targetView() {
+ roundable.requestRoundness(1f, 1f, SOURCE1)
+ assertEquals(1f, roundable.roundableState.topRoundness)
+ assertEquals(1f, roundable.roundableState.bottomRoundness)
+
+ roundable.requestRoundnessReset(sourceType = SOURCE1)
+
+ assertEquals(0f, roundable.roundableState.topRoundness)
+ assertEquals(0f, roundable.roundableState.bottomRoundness)
+ verify(targetView, atLeastOnce()).invalidate()
+ }
+
+ @Test
+ fun hasRoundedCorner_return_true_ifRoundnessIsGreaterThenZero() {
+ roundable.requestRoundness(top = 1f, bottom = 1f, sourceType = SOURCE1)
+ assertEquals(true, roundable.hasRoundedCorner())
+
+ roundable.requestRoundness(top = 1f, bottom = 0f, sourceType = SOURCE1)
+ assertEquals(true, roundable.hasRoundedCorner())
+
+ roundable.requestRoundness(top = 0f, bottom = 1f, sourceType = SOURCE1)
+ assertEquals(true, roundable.hasRoundedCorner())
+
+ roundable.requestRoundness(top = 0f, bottom = 0f, sourceType = SOURCE1)
+ assertEquals(false, roundable.hasRoundedCorner())
+ }
+
+ @Test
+ fun roundness_take_maxValue_onMultipleSources_first_lower() {
+ roundable.requestRoundness(0.1f, 0.1f, SOURCE1)
+ assertEquals(0.1f, roundable.roundableState.topRoundness)
+ assertEquals(0.1f, roundable.roundableState.bottomRoundness)
+
+ roundable.requestRoundness(0.2f, 0.2f, SOURCE2)
+ // SOURCE1 has 0.1f - SOURCE2 has 0.2f
+ assertEquals(0.2f, roundable.roundableState.topRoundness)
+ assertEquals(0.2f, roundable.roundableState.bottomRoundness)
+ }
+
+ @Test
+ fun roundness_take_maxValue_onMultipleSources_first_higher() {
+ roundable.requestRoundness(0.5f, 0.5f, SOURCE1)
+ assertEquals(0.5f, roundable.roundableState.topRoundness)
+ assertEquals(0.5f, roundable.roundableState.bottomRoundness)
+
+ roundable.requestRoundness(0.1f, 0.1f, SOURCE2)
+ // SOURCE1 has 0.5f - SOURCE2 has 0.1f
+ assertEquals(0.5f, roundable.roundableState.topRoundness)
+ assertEquals(0.5f, roundable.roundableState.bottomRoundness)
+ }
+
+ @Test
+ fun roundness_take_maxValue_onMultipleSources_first_higher_second_step() {
+ roundable.requestRoundness(0.1f, 0.1f, SOURCE1)
+ assertEquals(0.1f, roundable.roundableState.topRoundness)
+ assertEquals(0.1f, roundable.roundableState.bottomRoundness)
+
+ roundable.requestRoundness(0.2f, 0.2f, SOURCE2)
+ // SOURCE1 has 0.1f - SOURCE2 has 0.2f
+ assertEquals(0.2f, roundable.roundableState.topRoundness)
+ assertEquals(0.2f, roundable.roundableState.bottomRoundness)
+
+ roundable.requestRoundness(0.3f, 0.3f, SOURCE1)
+ // SOURCE1 has 0.3f - SOURCE2 has 0.2f
+ assertEquals(0.3f, roundable.roundableState.topRoundness)
+ assertEquals(0.3f, roundable.roundableState.bottomRoundness)
+ }
+
+ @Test
+ fun roundness_take_maxValue_onMultipleSources_first_lower_second_step() {
+ roundable.requestRoundness(0.5f, 0.5f, SOURCE1)
+ assertEquals(0.5f, roundable.roundableState.topRoundness)
+ assertEquals(0.5f, roundable.roundableState.bottomRoundness)
+
+ roundable.requestRoundness(0.2f, 0.2f, SOURCE2)
+ // SOURCE1 has 0.5f - SOURCE2 has 0.2f
+ assertEquals(0.5f, roundable.roundableState.topRoundness)
+ assertEquals(0.5f, roundable.roundableState.bottomRoundness)
+
+ roundable.requestRoundness(0.1f, 0.1f, SOURCE1)
+ // SOURCE1 has 0.1f - SOURCE2 has 0.2f
+ assertEquals(0.2f, roundable.roundableState.topRoundness)
+ assertEquals(0.2f, roundable.roundableState.bottomRoundness)
+ }
+
+ class FakeRoundable(
+ targetView: View,
+ radius: Float = MAX_RADIUS,
+ ) : Roundable {
+ override val roundableState =
+ RoundableState(
+ targetView = targetView,
+ roundable = this,
+ maxRadius = radius,
+ )
+ }
+
+ companion object {
+ private const val MAX_RADIUS = 10f
+ private val SOURCE1 = SourceType.from("Source1")
+ private val SOURCE2 = SourceType.from("Source2")
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
index bdedd244abfa..7f73856bff89 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
@@ -30,6 +30,8 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntryB
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
import com.android.systemui.statusbar.notification.collection.provider.SectionHeaderVisibilityProvider
+import com.android.systemui.statusbar.notification.collection.provider.SeenNotificationsProvider
+import com.android.systemui.statusbar.notification.collection.provider.SeenNotificationsProviderImpl
import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
@@ -103,6 +105,31 @@ class KeyguardCoordinatorTest : SysuiTestCase() {
}
@Test
+ fun unseenFilterUpdatesSeenProviderWhenSuppressing() {
+ whenever(notifPipelineFlags.shouldFilterUnseenNotifsOnKeyguard).thenReturn(true)
+
+ // GIVEN: Keyguard is not showing, and a notification is present
+ keyguardRepository.setKeyguardShowing(false)
+ runKeyguardCoordinatorTest {
+ val fakeEntry = NotificationEntryBuilder().build()
+ collectionListener.onEntryAdded(fakeEntry)
+
+ // WHEN: The keyguard is now showing
+ keyguardRepository.setKeyguardShowing(true)
+ testScheduler.runCurrent()
+
+ // THEN: The notification is recognized as "seen" and is filtered out.
+ assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isTrue()
+
+ // WHEN: The filter is cleaned up
+ unseenFilter.onCleanup()
+
+ // THEN: The SeenNotificationProvider has been updated to reflect the suppression
+ assertThat(seenNotificationsProvider.hasFilteredOutSeenNotifications).isTrue()
+ }
+ }
+
+ @Test
fun unseenFilterAllowsNewNotif() {
whenever(notifPipelineFlags.shouldFilterUnseenNotifsOnKeyguard).thenReturn(true)
@@ -204,6 +231,7 @@ class KeyguardCoordinatorTest : SysuiTestCase() {
testBlock: suspend KeyguardCoordinatorTestScope.() -> Unit
) {
val testScope = TestScope(UnconfinedTestDispatcher())
+ val seenNotificationsProvider = SeenNotificationsProviderImpl()
val keyguardCoordinator =
KeyguardCoordinator(
keyguardNotifVisibilityProvider,
@@ -211,18 +239,20 @@ class KeyguardCoordinatorTest : SysuiTestCase() {
notifPipelineFlags,
testScope.backgroundScope,
sectionHeaderVisibilityProvider,
+ seenNotificationsProvider,
statusBarStateController,
)
keyguardCoordinator.attach(notifPipeline)
- KeyguardCoordinatorTestScope(keyguardCoordinator, testScope).run {
- testScheduler.advanceUntilIdle()
- testScope.runTest(dispatchTimeoutMs = 1.seconds.inWholeMilliseconds) { testBlock() }
+ testScope.runTest(dispatchTimeoutMs = 1.seconds.inWholeMilliseconds) {
+ KeyguardCoordinatorTestScope(keyguardCoordinator, testScope, seenNotificationsProvider)
+ .testBlock()
}
}
private inner class KeyguardCoordinatorTestScope(
private val keyguardCoordinator: KeyguardCoordinator,
private val scope: TestScope,
+ val seenNotificationsProvider: SeenNotificationsProvider,
) : CoroutineScope by scope {
val testScheduler: TestCoroutineScheduler
get() = scope.testScheduler
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index 088d1654d1f0..59d4720fdb6d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -213,8 +213,7 @@ public class NotificationTestHelper {
SourceType sourceType
) throws Exception {
ExpandableNotificationRow row = createRow();
- row.requestTopRoundness(topRoundness, false, sourceType);
- row.requestBottomRoundness(bottomRoundness, /*animate = */ false, sourceType);
+ row.requestRoundness(topRoundness, bottomRoundness, sourceType, /*animate = */ false);
assertEquals(topRoundness, row.getTopRoundness(), /* delta = */ 0f);
assertEquals(bottomRoundness, row.getBottomRoundness(), /* delta = */ 0f);
return row;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java
index 438b528944be..fd1944e7478d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java
@@ -25,7 +25,7 @@ import android.view.View;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.notification.SourceType;
+import com.android.systemui.statusbar.notification.LegacySourceType;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
@@ -158,7 +158,7 @@ public class NotificationChildrenContainerTest extends SysuiTestCase {
ExpandableNotificationRow row = mNotificationTestHelper.createRowWithRoundness(
/* topRoundness = */ 1f,
/* bottomRoundness = */ 1f,
- /* sourceType = */ SourceType.OnScroll);
+ /* sourceType = */ LegacySourceType.OnScroll);
mChildrenContainer.addNotification(row, 0);
@@ -171,11 +171,11 @@ public class NotificationChildrenContainerTest extends SysuiTestCase {
ExpandableNotificationRow row1 = mNotificationTestHelper.createRowWithRoundness(
/* topRoundness = */ 1f,
/* bottomRoundness = */ 1f,
- /* sourceType = */ SourceType.DefaultValue);
+ /* sourceType = */ LegacySourceType.DefaultValue);
ExpandableNotificationRow row2 = mNotificationTestHelper.createRowWithRoundness(
/* topRoundness = */ 1f,
/* bottomRoundness = */ 1f,
- /* sourceType = */ SourceType.OnDismissAnimation);
+ /* sourceType = */ LegacySourceType.OnDismissAnimation);
mChildrenContainer.addNotification(row1, 0);
mChildrenContainer.addNotification(row2, 0);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java
index 8c8b64424814..bd0a556ac670 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java
@@ -35,6 +35,7 @@ import androidx.test.filters.SmallTest;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.logging.NotificationRoundnessLogger;
@@ -73,7 +74,8 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase {
mRoundnessManager = new NotificationRoundnessManager(
new NotificationSectionsFeatureManager(new DeviceConfigProxy(), mContext),
mLogger,
- mock(DumpManager.class));
+ mock(DumpManager.class),
+ mock(FeatureFlags.class));
allowTestableLooperAsMainThread();
NotificationTestHelper testHelper = new NotificationTestHelper(
mContext,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
index ecc02246ea1d..30da08ebfe7d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
@@ -30,6 +30,7 @@ import android.view.ViewGroup;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.media.controls.ui.KeyguardMediaController;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.StatusBarState;
@@ -59,10 +60,12 @@ public class NotificationSectionsManagerTest extends SysuiTestCase {
@Mock private KeyguardMediaController mKeyguardMediaController;
@Mock private NotificationSectionsFeatureManager mSectionsFeatureManager;
@Mock private MediaContainerController mMediaContainerController;
+ @Mock private NotificationRoundnessManager mNotificationRoundnessManager;
@Mock private SectionHeaderController mIncomingHeaderController;
@Mock private SectionHeaderController mPeopleHeaderController;
@Mock private SectionHeaderController mAlertingHeaderController;
@Mock private SectionHeaderController mSilentHeaderController;
+ @Mock private FeatureFlags mFeatureFlag;
private NotificationSectionsManager mSectionsManager;
@@ -89,10 +92,12 @@ public class NotificationSectionsManagerTest extends SysuiTestCase {
mKeyguardMediaController,
mSectionsFeatureManager,
mMediaContainerController,
+ mNotificationRoundnessManager,
mIncomingHeaderController,
mPeopleHeaderController,
mAlertingHeaderController,
- mSilentHeaderController
+ mSilentHeaderController,
+ mFeatureFlag
);
// Required in order for the header inflation to work properly
when(mNssl.generateLayoutParams(any(AttributeSet.class)))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
index bda233611158..9d759c4b6016 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
@@ -9,7 +9,7 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.ShadeInterpolation
import com.android.systemui.statusbar.NotificationShelf
import com.android.systemui.statusbar.StatusBarIconView
-import com.android.systemui.statusbar.notification.SourceType
+import com.android.systemui.statusbar.notification.LegacySourceType
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.ExpandableView
import com.android.systemui.statusbar.notification.row.NotificationTestHelper
@@ -314,9 +314,9 @@ class NotificationShelfTest : SysuiTestCase() {
val row: ExpandableNotificationRow = notificationTestHelper.createRowWithRoundness(
/* topRoundness = */ 1f,
/* bottomRoundness = */ 1f,
- /* sourceType = */ SourceType.OnScroll)
+ /* sourceType = */ LegacySourceType.OnScroll)
- NotificationShelf.resetOnScrollRoundness(row)
+ NotificationShelf.resetLegacyOnScrollRoundness(row)
assertEquals(0f, row.topRoundness)
assertEquals(0f, row.bottomRoundness)
@@ -327,14 +327,14 @@ class NotificationShelfTest : SysuiTestCase() {
val row1: ExpandableNotificationRow = notificationTestHelper.createRowWithRoundness(
/* topRoundness = */ 1f,
/* bottomRoundness = */ 1f,
- /* sourceType = */ SourceType.DefaultValue)
+ /* sourceType = */ LegacySourceType.DefaultValue)
val row2: ExpandableNotificationRow = notificationTestHelper.createRowWithRoundness(
/* topRoundness = */ 1f,
/* bottomRoundness = */ 1f,
- /* sourceType = */ SourceType.OnDismissAnimation)
+ /* sourceType = */ LegacySourceType.OnDismissAnimation)
- NotificationShelf.resetOnScrollRoundness(row1)
- NotificationShelf.resetOnScrollRoundness(row2)
+ NotificationShelf.resetLegacyOnScrollRoundness(row1)
+ NotificationShelf.resetLegacyOnScrollRoundness(row2)
assertEquals(1f, row1.topRoundness)
assertEquals(1f, row1.bottomRoundness)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index 026c82eda42d..645052feee94 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -59,8 +59,10 @@ import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
+import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.statusbar.notification.collection.NotifCollection;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
+import com.android.systemui.statusbar.notification.collection.provider.SeenNotificationsProviderImpl;
import com.android.systemui.statusbar.notification.collection.provider.VisibilityLocationProviderDelegator;
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
@@ -119,6 +121,7 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
@Mock private GroupExpansionManager mGroupExpansionManager;
@Mock private SectionHeaderController mSilentHeaderController;
@Mock private NotifPipeline mNotifPipeline;
+ @Mock private NotifPipelineFlags mNotifPipelineFlags;
@Mock private NotifCollection mNotifCollection;
@Mock private UiEventLogger mUiEventLogger;
@Mock private LockscreenShadeTransitionController mLockscreenShadeTransitionController;
@@ -170,12 +173,14 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
mGroupExpansionManager,
mSilentHeaderController,
mNotifPipeline,
+ mNotifPipelineFlags,
mNotifCollection,
mLockscreenShadeTransitionController,
mShadeTransitionController,
mUiEventLogger,
mRemoteInputManager,
mVisibilityLocationProviderDelegator,
+ new SeenNotificationsProviderImpl(),
mShadeController,
mJankMonitor,
mStackLogger,
@@ -228,14 +233,16 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
mController.updateShowEmptyShadeView();
verify(mNotificationStackScrollLayout).updateEmptyShadeView(
/* visible= */ true,
- /* notifVisibleInShade= */ true);
+ /* notifVisibleInShade= */ true,
+ /* areSeenNotifsFiltered= */false);
setupShowEmptyShadeViewState(false);
reset(mNotificationStackScrollLayout);
mController.updateShowEmptyShadeView();
verify(mNotificationStackScrollLayout).updateEmptyShadeView(
/* visible= */ false,
- /* notifVisibleInShade= */ true);
+ /* notifVisibleInShade= */ true,
+ /* areSeenNotifsFiltered= */false);
}
@Test
@@ -248,14 +255,16 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
mController.updateShowEmptyShadeView();
verify(mNotificationStackScrollLayout).updateEmptyShadeView(
/* visible= */ true,
- /* notifVisibleInShade= */ false);
+ /* notifVisibleInShade= */ false,
+ /* areSeenNotifsFiltered= */false);
setupShowEmptyShadeViewState(false);
reset(mNotificationStackScrollLayout);
mController.updateShowEmptyShadeView();
verify(mNotificationStackScrollLayout).updateEmptyShadeView(
/* visible= */ false,
- /* notifVisibleInShade= */ false);
+ /* notifVisibleInShade= */ false,
+ /* areSeenNotifsFiltered= */false);
}
@Test
@@ -274,14 +283,16 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
mController.updateShowEmptyShadeView();
verify(mNotificationStackScrollLayout).updateEmptyShadeView(
/* visible= */ true,
- /* notifVisibleInShade= */ false);
+ /* notifVisibleInShade= */ false,
+ /* areSeenNotifsFiltered= */false);
mController.setQsFullScreen(true);
reset(mNotificationStackScrollLayout);
mController.updateShowEmptyShadeView();
verify(mNotificationStackScrollLayout).updateEmptyShadeView(
/* visible= */ true,
- /* notifVisibleInShade= */ false);
+ /* notifVisibleInShade= */ false,
+ /* areSeenNotifsFiltered= */false);
}
@Test
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 dceb4ff48125..07ea6304f7cd 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
@@ -324,7 +324,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
public void updateEmptyView_dndSuppressing() {
when(mEmptyShadeView.willBeGone()).thenReturn(true);
- mStackScroller.updateEmptyShadeView(true, true);
+ mStackScroller.updateEmptyShadeView(true, true, false);
verify(mEmptyShadeView).setText(R.string.dnd_suppressing_shade_text);
}
@@ -334,7 +334,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
mStackScroller.setEmptyShadeView(mEmptyShadeView);
when(mEmptyShadeView.willBeGone()).thenReturn(true);
- mStackScroller.updateEmptyShadeView(true, false);
+ mStackScroller.updateEmptyShadeView(true, false, false);
verify(mEmptyShadeView).setText(R.string.empty_shade_text);
}
@@ -343,10 +343,10 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
public void updateEmptyView_noNotificationsToDndSuppressing() {
mStackScroller.setEmptyShadeView(mEmptyShadeView);
when(mEmptyShadeView.willBeGone()).thenReturn(true);
- mStackScroller.updateEmptyShadeView(true, false);
+ mStackScroller.updateEmptyShadeView(true, false, false);
verify(mEmptyShadeView).setText(R.string.empty_shade_text);
- mStackScroller.updateEmptyShadeView(true, true);
+ mStackScroller.updateEmptyShadeView(true, true, false);
verify(mEmptyShadeView).setText(R.string.dnd_suppressing_shade_text);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
index 4ea1c7100557..680a32375988 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
@@ -74,6 +74,7 @@ public class NotificationSwipeHelperTest extends SysuiTestCase {
private NotificationSwipeHelper mSwipeHelper;
private NotificationSwipeHelper.NotificationCallback mCallback;
private NotificationMenuRowPlugin.OnMenuEventListener mListener;
+ private NotificationRoundnessManager mNotificationRoundnessManager;
private View mView;
private MotionEvent mEvent;
private NotificationMenuRowPlugin mMenuRow;
@@ -92,10 +93,17 @@ public class NotificationSwipeHelperTest extends SysuiTestCase {
public void setUp() throws Exception {
mCallback = mock(NotificationSwipeHelper.NotificationCallback.class);
mListener = mock(NotificationMenuRowPlugin.OnMenuEventListener.class);
+ mNotificationRoundnessManager = mock(NotificationRoundnessManager.class);
mFeatureFlags = mock(FeatureFlags.class);
mSwipeHelper = spy(new NotificationSwipeHelper(
- mContext.getResources(), ViewConfiguration.get(mContext),
- new FalsingManagerFake(), mFeatureFlags, SwipeHelper.X, mCallback, mListener));
+ mContext.getResources(),
+ ViewConfiguration.get(mContext),
+ new FalsingManagerFake(),
+ mFeatureFlags,
+ SwipeHelper.X,
+ mCallback,
+ mListener,
+ mNotificationRoundnessManager));
mView = mock(View.class);
mEvent = mock(MotionEvent.class);
mMenuRow = mock(NotificationMenuRowPlugin.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelperTest.kt
index a2e92305bf27..81a3f1245efe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelperTest.kt
@@ -35,7 +35,7 @@ class NotificationTargetsHelperTest : SysuiTestCase() {
) =
NotificationTargetsHelper(
FakeFeatureFlags().apply {
- set(Flags.NOTIFICATION_GROUP_CORNER, notificationGroupCorner)
+ set(Flags.USE_ROUNDNESS_SOURCETYPES, notificationGroupCorner)
}
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index ed84e4268c90..521e51846834 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -106,6 +106,7 @@ import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.keyguard.ui.viewmodel.LightRevealScrimViewModel;
import com.android.systemui.navigationbar.NavigationBarController;
import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
import com.android.systemui.plugins.PluginDependencyProvider;
@@ -212,6 +213,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
@Mock private NotificationPanelView mNotificationPanelView;
@Mock private IStatusBarService mBarService;
@Mock private IDreamManager mDreamManager;
+ @Mock private LightRevealScrimViewModel mLightRevealScrimViewModel;
@Mock private ScrimController mScrimController;
@Mock private DozeScrimController mDozeScrimController;
@Mock private Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy;
@@ -497,7 +499,8 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
mDeviceStateManager,
mWiredChargingRippleController,
mDreamManager,
- mCameraLauncherLazy) {
+ mCameraLauncherLazy,
+ () -> mLightRevealScrimViewModel) {
@Override
protected ViewRootImpl getViewRootImpl() {
return mViewRootImpl;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt
index 038af8ff5396..6155e3c16996 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt
@@ -27,6 +27,7 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
import com.google.common.truth.Truth.assertThat
import org.junit.Before
+import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.doAnswer
@@ -295,6 +296,7 @@ class ConfigurationControllerImplTest : SysuiTestCase() {
}
@Test
+ @Ignore("b/261408895")
fun equivalentConfigObject_listenerNotNotified() {
val config = mContext.resources.configuration
val listener = createAndAddListener()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
index 103b7b4268de..9727b6c5eb83 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
@@ -32,6 +32,7 @@ import android.widget.TextView;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shade.NotificationPanelViewController;
@@ -40,6 +41,7 @@ import com.android.systemui.statusbar.HeadsUpStatusBarView;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
+import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.policy.Clock;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -71,6 +73,8 @@ public class HeadsUpAppearanceControllerTest extends SysuiTestCase {
private NotificationWakeUpCoordinator mWakeUpCoordinator;
private KeyguardStateController mKeyguardStateController;
private CommandQueue mCommandQueue;
+ private NotificationRoundnessManager mNotificationRoundnessManager;
+ private FeatureFlags mFeatureFlag;
@Before
public void setUp() throws Exception {
@@ -89,6 +93,8 @@ public class HeadsUpAppearanceControllerTest extends SysuiTestCase {
mWakeUpCoordinator = mock(NotificationWakeUpCoordinator.class);
mKeyguardStateController = mock(KeyguardStateController.class);
mCommandQueue = mock(CommandQueue.class);
+ mNotificationRoundnessManager = mock(NotificationRoundnessManager.class);
+ mFeatureFlag = mock(FeatureFlags.class);
mHeadsUpAppearanceController = new HeadsUpAppearanceController(
mock(NotificationIconAreaController.class),
mHeadsUpManager,
@@ -100,6 +106,8 @@ public class HeadsUpAppearanceControllerTest extends SysuiTestCase {
mCommandQueue,
mStackScrollerController,
mPanelView,
+ mNotificationRoundnessManager,
+ mFeatureFlag,
mHeadsUpStatusBarView,
new Clock(mContext, null),
Optional.of(mOperatorNameView));
@@ -182,6 +190,8 @@ public class HeadsUpAppearanceControllerTest extends SysuiTestCase {
mCommandQueue,
mStackScrollerController,
mPanelView,
+ mNotificationRoundnessManager,
+ mFeatureFlag,
mHeadsUpStatusBarView,
new Clock(mContext, null),
Optional.empty());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImplTest.kt
new file mode 100644
index 000000000000..7eba3b463336
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImplTest.kt
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone
+
+import android.content.pm.UserInfo
+import android.os.UserManager
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import junit.framework.Assert
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+class ManagedProfileControllerImplTest : SysuiTestCase() {
+
+ private val mainExecutor: FakeExecutor = FakeExecutor(FakeSystemClock())
+
+ private lateinit var controller: ManagedProfileControllerImpl
+
+ @Mock private lateinit var userTracker: UserTracker
+ @Mock private lateinit var userManager: UserManager
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+
+ controller = ManagedProfileControllerImpl(context, mainExecutor, userTracker, userManager)
+ }
+
+ @Test
+ fun hasWorkingProfile_isWorkModeEnabled_returnsTrue() {
+ `when`(userTracker.userId).thenReturn(1)
+ setupWorkingProfile(1)
+
+ Assert.assertEquals(true, controller.hasActiveProfile())
+ }
+
+ @Test
+ fun noWorkingProfile_isWorkModeEnabled_returnsFalse() {
+ `when`(userTracker.userId).thenReturn(1)
+
+ Assert.assertEquals(false, controller.hasActiveProfile())
+ }
+
+ @Test
+ fun listeningUserChanges_isWorkModeEnabled_returnsTrue() {
+ `when`(userTracker.userId).thenReturn(1)
+ controller.addCallback(TestCallback)
+ `when`(userTracker.userId).thenReturn(2)
+ setupWorkingProfile(2)
+
+ Assert.assertEquals(true, controller.hasActiveProfile())
+ }
+
+ private fun setupWorkingProfile(userId: Int) {
+ `when`(userManager.getEnabledProfiles(userId))
+ .thenReturn(
+ listOf(UserInfo(userId, "test_user", "", 0, UserManager.USER_TYPE_PROFILE_MANAGED))
+ )
+ }
+
+ private object TestCallback : ManagedProfileController.Callback {
+
+ override fun onManagedProfileChanged() = Unit
+
+ override fun onManagedProfileRemoved() = Unit
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
index e2843a12c51f..14d239ab48cd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
@@ -27,6 +27,8 @@ import androidx.test.platform.app.InstrumentationRegistry
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.shade.NotificationPanelViewController
+import com.android.systemui.shade.ShadeControllerImpl
+import com.android.systemui.shade.ShadeLogger
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.unfold.SysUIUnfoldComponent
import com.android.systemui.unfold.config.UnfoldTransitionConfig
@@ -41,6 +43,7 @@ import org.mockito.ArgumentCaptor
import org.mockito.Mock
import org.mockito.Mockito.`when`
import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
import org.mockito.Mockito.spy
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@@ -49,8 +52,6 @@ import java.util.Optional
@SmallTest
class PhoneStatusBarViewControllerTest : SysuiTestCase() {
- private val touchEventHandler = TestTouchEventHandler()
-
@Mock
private lateinit var notificationPanelViewController: NotificationPanelViewController
@Mock
@@ -66,6 +67,12 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() {
@Mock
private lateinit var userChipViewModel: StatusBarUserChipViewModel
@Mock
+ private lateinit var centralSurfacesImpl: CentralSurfacesImpl
+ @Mock
+ private lateinit var shadeControllerImpl: ShadeControllerImpl
+ @Mock
+ private lateinit var shadeLogger: ShadeLogger
+ @Mock
private lateinit var viewUtil: ViewUtil
private lateinit var view: PhoneStatusBarView
@@ -88,18 +95,6 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() {
}
@Test
- fun constructor_setsTouchHandlerOnView() {
- val interceptEvent = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 10f, 10f, 0)
- val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
-
- view.onInterceptTouchEvent(interceptEvent)
- view.onTouchEvent(event)
-
- assertThat(touchEventHandler.lastInterceptEvent).isEqualTo(interceptEvent)
- assertThat(touchEventHandler.lastEvent).isEqualTo(event)
- }
-
- @Test
fun onViewAttachedAndDrawn_moveFromCenterAnimationEnabled_moveFromCenterAnimationInitialized() {
val view = createViewMock()
val argumentCaptor = ArgumentCaptor.forClass(OnPreDrawListener::class.java)
@@ -115,6 +110,66 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() {
verify(moveFromCenterAnimation).onViewsReady(any())
}
+ @Test
+ fun handleTouchEventFromStatusBar_panelsNotEnabled_returnsFalseAndNoViewEvent() {
+ `when`(centralSurfacesImpl.commandQueuePanelsEnabled).thenReturn(false)
+ val returnVal = view.onTouchEvent(
+ MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0))
+ assertThat(returnVal).isFalse()
+ verify(notificationPanelViewController, never()).sendTouchEventToView(any())
+ }
+
+ @Test
+ fun handleTouchEventFromStatusBar_viewNotEnabled_returnsTrueAndNoViewEvent() {
+ `when`(centralSurfacesImpl.commandQueuePanelsEnabled).thenReturn(true)
+ `when`(centralSurfacesImpl.notificationPanelViewController)
+ .thenReturn(notificationPanelViewController)
+ `when`(notificationPanelViewController.isViewEnabled).thenReturn(false)
+ val returnVal = view.onTouchEvent(
+ MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0))
+ assertThat(returnVal).isTrue()
+ verify(notificationPanelViewController, never()).sendTouchEventToView(any())
+ }
+
+ @Test
+ fun handleTouchEventFromStatusBar_viewNotEnabledButIsMoveEvent_viewReceivesEvent() {
+ `when`(centralSurfacesImpl.commandQueuePanelsEnabled).thenReturn(true)
+ `when`(centralSurfacesImpl.notificationPanelViewController)
+ .thenReturn(notificationPanelViewController)
+ `when`(notificationPanelViewController.isViewEnabled).thenReturn(false)
+ val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0)
+
+ view.onTouchEvent(event)
+
+ verify(notificationPanelViewController).sendTouchEventToView(event)
+ }
+
+ @Test
+ fun handleTouchEventFromStatusBar_panelAndViewEnabled_viewReceivesEvent() {
+ `when`(centralSurfacesImpl.commandQueuePanelsEnabled).thenReturn(true)
+ `when`(centralSurfacesImpl.notificationPanelViewController)
+ .thenReturn(notificationPanelViewController)
+ `when`(notificationPanelViewController.isViewEnabled).thenReturn(true)
+ val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 2f, 0)
+
+ view.onTouchEvent(event)
+
+ verify(notificationPanelViewController).sendTouchEventToView(event)
+ }
+
+ @Test
+ fun handleTouchEventFromStatusBar_topEdgeTouch_viewNeverReceivesEvent() {
+ `when`(centralSurfacesImpl.commandQueuePanelsEnabled).thenReturn(true)
+ `when`(centralSurfacesImpl.notificationPanelViewController)
+ .thenReturn(notificationPanelViewController)
+ `when`(notificationPanelViewController.isFullyCollapsed).thenReturn(true)
+ val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
+
+ view.onTouchEvent(event)
+
+ verify(notificationPanelViewController, never()).sendTouchEventToView(any())
+ }
+
private fun createViewMock(): PhoneStatusBarView {
val view = spy(view)
val viewTreeObserver = mock(ViewTreeObserver::class.java)
@@ -128,9 +183,12 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() {
Optional.of(sysuiUnfoldComponent),
Optional.of(progressProvider),
userChipViewModel,
+ centralSurfacesImpl,
+ shadeControllerImpl,
+ shadeLogger,
viewUtil,
configurationController
- ).create(view, touchEventHandler).also {
+ ).create(view).also {
it.init()
}
}
@@ -140,17 +198,4 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() {
override var isHingeAngleEnabled: Boolean = false
override val halfFoldedTimeoutMillis: Int = 0
}
-
- private class TestTouchEventHandler : PhoneStatusBarView.TouchEventHandler {
- var lastEvent: MotionEvent? = null
- var lastInterceptEvent: MotionEvent? = null
-
- override fun onInterceptTouchEvent(event: MotionEvent?) {
- lastInterceptEvent = event
- }
- override fun handleTouchEvent(event: MotionEvent?): Boolean {
- lastEvent = event
- return false
- }
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index e467d9399059..14a319bc87e9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -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 com.android.systemui.statusbar.phone;
@@ -105,7 +105,6 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
@Mock private KeyguardBouncer.Factory mKeyguardBouncerFactory;
@Mock private KeyguardMessageAreaController.Factory mKeyguardMessageAreaFactory;
@Mock private KeyguardMessageAreaController mKeyguardMessageAreaController;
- @Mock private KeyguardBouncer mPrimaryBouncer;
@Mock private StatusBarKeyguardViewManager.AlternateBouncer mAlternateBouncer;
@Mock private KeyguardMessageArea mKeyguardMessageArea;
@Mock private ShadeController mShadeController;
@@ -133,16 +132,14 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- when(mKeyguardBouncerFactory.create(
- any(ViewGroup.class),
- any(KeyguardBouncer.PrimaryBouncerExpansionCallback.class)))
- .thenReturn(mPrimaryBouncer);
when(mCentralSurfaces.getBouncerContainer()).thenReturn(mContainer);
when(mContainer.findViewById(anyInt())).thenReturn(mKeyguardMessageArea);
when(mKeyguardMessageAreaFactory.create(any(KeyguardMessageArea.class)))
.thenReturn(mKeyguardMessageAreaController);
when(mBouncerView.getDelegate()).thenReturn(mBouncerViewDelegate);
+ when(mFeatureFlags.isEnabled(MODERN_BOUNCER)).thenReturn(true);
+
mStatusBarKeyguardViewManager =
new StatusBarKeyguardViewManager(
getContext(),
@@ -184,7 +181,7 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
mStatusBarKeyguardViewManager.show(null);
ArgumentCaptor<KeyguardBouncer.PrimaryBouncerExpansionCallback> callbackArgumentCaptor =
ArgumentCaptor.forClass(KeyguardBouncer.PrimaryBouncerExpansionCallback.class);
- verify(mKeyguardBouncerFactory).create(any(ViewGroup.class),
+ verify(mPrimaryBouncerCallbackInteractor).addBouncerExpansionCallback(
callbackArgumentCaptor.capture());
mBouncerExpansionCallback = callbackArgumentCaptor.getValue();
}
@@ -195,86 +192,87 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
Runnable cancelAction = () -> {};
mStatusBarKeyguardViewManager.dismissWithAction(
action, cancelAction, false /* afterKeyguardGone */);
- verify(mPrimaryBouncer).showWithDismissAction(eq(action), eq(cancelAction));
+ verify(mPrimaryBouncerInteractor).setDismissAction(eq(action), eq(cancelAction));
+ verify(mPrimaryBouncerInteractor).show(eq(true));
}
@Test
public void showBouncer_onlyWhenShowing() {
mStatusBarKeyguardViewManager.hide(0 /* startTime */, 0 /* fadeoutDuration */);
mStatusBarKeyguardViewManager.showPrimaryBouncer(true /* scrimmed */);
- verify(mPrimaryBouncer, never()).show(anyBoolean(), anyBoolean());
- verify(mPrimaryBouncer, never()).show(anyBoolean());
+ verify(mPrimaryBouncerInteractor, never()).show(anyBoolean());
}
@Test
public void showBouncer_notWhenBouncerAlreadyShowing() {
mStatusBarKeyguardViewManager.hide(0 /* startTime */, 0 /* fadeoutDuration */);
- when(mPrimaryBouncer.isSecure()).thenReturn(true);
+ when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(
+ KeyguardSecurityModel.SecurityMode.Password);
mStatusBarKeyguardViewManager.showPrimaryBouncer(true /* scrimmed */);
- verify(mPrimaryBouncer, never()).show(anyBoolean(), anyBoolean());
- verify(mPrimaryBouncer, never()).show(anyBoolean());
+ verify(mPrimaryBouncerInteractor, never()).show(anyBoolean());
}
@Test
public void showBouncer_showsTheBouncer() {
mStatusBarKeyguardViewManager.showPrimaryBouncer(true /* scrimmed */);
- verify(mPrimaryBouncer).show(anyBoolean(), eq(true));
+ verify(mPrimaryBouncerInteractor).show(eq(true));
}
@Test
- public void onPanelExpansionChanged_neverHidesFullscreenBouncer() {
- when(mPrimaryBouncer.isShowing()).thenReturn(true);
- when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(
- KeyguardSecurityModel.SecurityMode.SimPuk);
- mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
- verify(mPrimaryBouncer).setExpansion(eq(KeyguardBouncer.EXPANSION_VISIBLE));
-
- reset(mPrimaryBouncer);
- when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(
- KeyguardSecurityModel.SecurityMode.SimPin);
+ public void onPanelExpansionChanged_neverShowsDuringHintAnimation() {
+ when(mNotificationPanelView.isUnlockHintRunning()).thenReturn(true);
mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
- verify(mPrimaryBouncer).setExpansion(eq(KeyguardBouncer.EXPANSION_VISIBLE));
+ verify(mPrimaryBouncerInteractor, never()).setPanelExpansion(anyFloat());
}
@Test
- public void onPanelExpansionChanged_neverShowsDuringHintAnimation() {
- when(mNotificationPanelView.isUnlockHintRunning()).thenReturn(true);
+ public void onPanelExpansionChanged_propagatesToBouncerOnlyIfShowing() {
mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
- verify(mPrimaryBouncer).setExpansion(eq(KeyguardBouncer.EXPANSION_HIDDEN));
+ verify(mPrimaryBouncerInteractor, never()).setPanelExpansion(eq(0.5f));
+
+ when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(true);
+ mStatusBarKeyguardViewManager.onPanelExpansionChanged(
+ expansionEvent(/* fraction= */ 0.6f, /* expanded= */ false, /* tracking= */ true));
+ verify(mPrimaryBouncerInteractor).setPanelExpansion(eq(0.6f));
}
@Test
- public void onPanelExpansionChanged_propagatesToBouncer() {
+ public void onPanelExpansionChanged_duplicateEventsAreIgnored() {
+ when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(true);
+ mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
+ verify(mPrimaryBouncerInteractor).setPanelExpansion(eq(0.5f));
+
+ reset(mPrimaryBouncerInteractor);
mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
- verify(mPrimaryBouncer).setExpansion(eq(0.5f));
+ verify(mPrimaryBouncerInteractor, never()).setPanelExpansion(eq(0.5f));
}
@Test
public void onPanelExpansionChanged_hideBouncer_afterKeyguardHidden() {
mStatusBarKeyguardViewManager.hide(0, 0);
- when(mPrimaryBouncer.inTransit()).thenReturn(true);
+ when(mPrimaryBouncerInteractor.isInTransit()).thenReturn(true);
mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
- verify(mPrimaryBouncer).setExpansion(eq(KeyguardBouncer.EXPANSION_HIDDEN));
+ verify(mPrimaryBouncerInteractor).setPanelExpansion(eq(KeyguardBouncer.EXPANSION_HIDDEN));
}
@Test
public void onPanelExpansionChanged_showsBouncerWhenSwiping() {
mKeyguardStateController.setCanDismissLockScreen(false);
mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
- verify(mPrimaryBouncer).show(eq(false), eq(false));
+ verify(mPrimaryBouncerInteractor).show(eq(false));
// But not when it's already visible
- reset(mPrimaryBouncer);
- when(mPrimaryBouncer.isShowing()).thenReturn(true);
+ reset(mPrimaryBouncerInteractor);
+ when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(true);
mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
- verify(mPrimaryBouncer, never()).show(eq(false), eq(false));
+ verify(mPrimaryBouncerInteractor, never()).show(eq(false));
// Or animating away
- reset(mPrimaryBouncer);
- when(mPrimaryBouncer.isAnimatingAway()).thenReturn(true);
+ reset(mPrimaryBouncerInteractor);
+ when(mPrimaryBouncerInteractor.isAnimatingAway()).thenReturn(true);
mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
- verify(mPrimaryBouncer, never()).show(eq(false), eq(false));
+ verify(mPrimaryBouncerInteractor, never()).show(eq(false));
}
@Test
@@ -286,7 +284,7 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
/* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE,
/* expanded= */ true,
/* tracking= */ false));
- verify(mPrimaryBouncer, never()).setExpansion(anyFloat());
+ verify(mPrimaryBouncerInteractor, never()).setPanelExpansion(anyFloat());
}
@Test
@@ -303,7 +301,7 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
/* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE,
/* expanded= */ true,
/* tracking= */ false));
- verify(mPrimaryBouncer, never()).setExpansion(anyFloat());
+ verify(mPrimaryBouncerInteractor, never()).setPanelExpansion(anyFloat());
}
@Test
@@ -314,7 +312,7 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
/* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE,
/* expanded= */ true,
/* tracking= */ false));
- verify(mPrimaryBouncer, never()).setExpansion(anyFloat());
+ verify(mPrimaryBouncerInteractor, never()).setPanelExpansion(anyFloat());
}
@Test
@@ -331,7 +329,7 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
/* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE,
/* expanded= */ true,
/* tracking= */ false));
- verify(mPrimaryBouncer, never()).setExpansion(anyFloat());
+ verify(mPrimaryBouncerInteractor, never()).setPanelExpansion(anyFloat());
}
@Test
@@ -342,7 +340,7 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
/* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE,
/* expanded= */ true,
/* tracking= */ false));
- verify(mPrimaryBouncer, never()).setExpansion(anyFloat());
+ verify(mPrimaryBouncerInteractor, never()).setPanelExpansion(anyFloat());
}
@Test
@@ -350,7 +348,7 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
mStatusBarKeyguardViewManager.setOccluded(false /* occluded */, true /* animated */);
verify(mCentralSurfaces).animateKeyguardUnoccluding();
- when(mPrimaryBouncer.isShowing()).thenReturn(true);
+ when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(true);
clearInvocations(mCentralSurfaces);
mStatusBarKeyguardViewManager.setOccluded(false /* occluded */, true /* animated */);
verify(mCentralSurfaces, never()).animateKeyguardUnoccluding();
@@ -401,7 +399,7 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
mStatusBarKeyguardViewManager.dismissWithAction(
action, cancelAction, true /* afterKeyguardGone */);
- when(mPrimaryBouncer.isShowing()).thenReturn(false);
+ when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(false);
mStatusBarKeyguardViewManager.hideBouncer(true);
mStatusBarKeyguardViewManager.hide(0, 30);
verify(action, never()).onDismiss();
@@ -415,7 +413,7 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
mStatusBarKeyguardViewManager.dismissWithAction(
action, cancelAction, true /* afterKeyguardGone */);
- when(mPrimaryBouncer.isShowing()).thenReturn(false);
+ when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(false);
mStatusBarKeyguardViewManager.hideBouncer(true);
verify(action, never()).onDismiss();
@@ -437,7 +435,7 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
@Test
public void testShowing_whenAlternateAuthShowing() {
mStatusBarKeyguardViewManager.setAlternateBouncer(mAlternateBouncer);
- when(mPrimaryBouncer.isShowing()).thenReturn(false);
+ when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(false);
when(mAlternateBouncer.isShowingAlternateBouncer()).thenReturn(true);
assertTrue(
"Is showing not accurate when alternative auth showing",
@@ -447,7 +445,7 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
@Test
public void testWillBeShowing_whenAlternateAuthShowing() {
mStatusBarKeyguardViewManager.setAlternateBouncer(mAlternateBouncer);
- when(mPrimaryBouncer.isShowing()).thenReturn(false);
+ when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(false);
when(mAlternateBouncer.isShowingAlternateBouncer()).thenReturn(true);
assertTrue(
"Is or will be showing not accurate when alternative auth showing",
@@ -458,7 +456,7 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
public void testHideAlternateBouncer_onShowBouncer() {
// GIVEN alt auth is showing
mStatusBarKeyguardViewManager.setAlternateBouncer(mAlternateBouncer);
- when(mPrimaryBouncer.isShowing()).thenReturn(false);
+ when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(false);
when(mAlternateBouncer.isShowingAlternateBouncer()).thenReturn(true);
reset(mAlternateBouncer);
@@ -471,8 +469,8 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
@Test
public void testBouncerIsOrWillBeShowing_whenBouncerIsInTransit() {
- when(mPrimaryBouncer.isShowing()).thenReturn(false);
- when(mPrimaryBouncer.inTransit()).thenReturn(true);
+ when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(false);
+ when(mPrimaryBouncerInteractor.isInTransit()).thenReturn(true);
assertTrue(
"Is or will be showing should be true when bouncer is in transit",
@@ -483,7 +481,7 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
public void testShowAltAuth_unlockingWithBiometricNotAllowed() {
// GIVEN alt auth exists, unlocking with biometric isn't allowed
mStatusBarKeyguardViewManager.setAlternateBouncer(mAlternateBouncer);
- when(mPrimaryBouncer.isShowing()).thenReturn(false);
+ when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(false);
when(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean()))
.thenReturn(false);
@@ -492,7 +490,7 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
mStatusBarKeyguardViewManager.showBouncer(scrimmed);
// THEN regular bouncer is shown
- verify(mPrimaryBouncer).show(anyBoolean(), eq(scrimmed));
+ verify(mPrimaryBouncerInteractor).show(eq(scrimmed));
verify(mAlternateBouncer, never()).showAlternateBouncer();
}
@@ -500,7 +498,7 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
public void testShowAlternateBouncer_unlockingWithBiometricAllowed() {
// GIVEN alt auth exists, unlocking with biometric is allowed
mStatusBarKeyguardViewManager.setAlternateBouncer(mAlternateBouncer);
- when(mPrimaryBouncer.isShowing()).thenReturn(false);
+ when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(false);
when(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
// WHEN showGenericBouncer is called
@@ -508,30 +506,28 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
// THEN alt auth bouncer is shown
verify(mAlternateBouncer).showAlternateBouncer();
- verify(mPrimaryBouncer, never()).show(anyBoolean(), anyBoolean());
+ verify(mPrimaryBouncerInteractor, never()).show(anyBoolean());
}
@Test
public void testUpdateResources_delegatesToBouncer() {
mStatusBarKeyguardViewManager.updateResources();
- verify(mPrimaryBouncer).updateResources();
+ verify(mPrimaryBouncerInteractor).updateResources();
}
@Test
public void updateKeyguardPosition_delegatesToBouncer() {
mStatusBarKeyguardViewManager.updateKeyguardPosition(1.0f);
- verify(mPrimaryBouncer).updateKeyguardPosition(1.0f);
+ verify(mPrimaryBouncerInteractor).setKeyguardPosition(1.0f);
}
@Test
public void testIsBouncerInTransit() {
- when(mPrimaryBouncer.inTransit()).thenReturn(true);
+ when(mPrimaryBouncerInteractor.isInTransit()).thenReturn(true);
Truth.assertThat(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).isTrue();
- when(mPrimaryBouncer.inTransit()).thenReturn(false);
- Truth.assertThat(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).isFalse();
- mPrimaryBouncer = null;
+ when(mPrimaryBouncerInteractor.isInTransit()).thenReturn(false);
Truth.assertThat(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).isFalse();
}
@@ -563,7 +559,7 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
eq(OnBackInvokedDispatcher.PRIORITY_OVERLAY),
mOnBackInvokedCallback.capture());
- when(mPrimaryBouncer.isShowing()).thenReturn(true);
+ when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(true);
when(mCentralSurfaces.shouldKeyguardHideImmediately()).thenReturn(true);
/* invoke the back callback directly */
mOnBackInvokedCallback.getValue().onBackInvoked();
@@ -593,13 +589,6 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
}
@Test
- public void flag_off_DoesNotCallBouncerInteractor() {
- when(mFeatureFlags.isEnabled(MODERN_BOUNCER)).thenReturn(false);
- mStatusBarKeyguardViewManager.hideBouncer(false);
- verify(mPrimaryBouncerInteractor, never()).hide();
- }
-
- @Test
public void hideAlternateBouncer_beforeCentralSurfacesRegistered() {
mStatusBarKeyguardViewManager =
new StatusBarKeyguardViewManager(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest_Old.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest_Old.java
new file mode 100644
index 000000000000..96fba39d6b59
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest_Old.java
@@ -0,0 +1,641 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone;
+
+import static com.android.systemui.flags.Flags.MODERN_BOUNCER;
+
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewRootImpl;
+import android.window.OnBackInvokedCallback;
+import android.window.OnBackInvokedDispatcher;
+import android.window.WindowOnBackInvokedDispatcher;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.util.LatencyTracker;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.keyguard.KeyguardMessageArea;
+import com.android.keyguard.KeyguardMessageAreaController;
+import com.android.keyguard.KeyguardSecurityModel;
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.ViewMediatorCallback;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dock.DockManager;
+import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.keyguard.data.BouncerView;
+import com.android.systemui.keyguard.data.BouncerViewDelegate;
+import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor;
+import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor;
+import com.android.systemui.navigationbar.NavigationModeController;
+import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
+import com.android.systemui.shade.NotificationPanelViewController;
+import com.android.systemui.shade.ShadeController;
+import com.android.systemui.shade.ShadeExpansionChangeEvent;
+import com.android.systemui.shade.ShadeExpansionStateManager;
+import com.android.systemui.statusbar.NotificationMediaManager;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
+import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.unfold.SysUIUnfoldComponent;
+
+import com.google.common.truth.Truth;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Optional;
+
+/**
+ * StatusBarKeyguardViewManager Test with deprecated KeyguardBouncer.java.
+ * TODO: Delete when deleting {@link KeyguardBouncer}
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class StatusBarKeyguardViewManagerTest_Old extends SysuiTestCase {
+ private static final ShadeExpansionChangeEvent EXPANSION_EVENT =
+ expansionEvent(/* fraction= */ 0.5f, /* expanded= */ false, /* tracking= */ true);
+
+ @Mock private ViewMediatorCallback mViewMediatorCallback;
+ @Mock private LockPatternUtils mLockPatternUtils;
+ @Mock private CentralSurfaces mCentralSurfaces;
+ @Mock private ViewGroup mContainer;
+ @Mock private NotificationPanelViewController mNotificationPanelView;
+ @Mock private BiometricUnlockController mBiometricUnlockController;
+ @Mock private SysuiStatusBarStateController mStatusBarStateController;
+ @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ @Mock private View mNotificationContainer;
+ @Mock private KeyguardBypassController mBypassController;
+ @Mock private KeyguardBouncer.Factory mKeyguardBouncerFactory;
+ @Mock private KeyguardMessageAreaController.Factory mKeyguardMessageAreaFactory;
+ @Mock private KeyguardMessageAreaController mKeyguardMessageAreaController;
+ @Mock private KeyguardBouncer mPrimaryBouncer;
+ @Mock private StatusBarKeyguardViewManager.AlternateBouncer mAlternateBouncer;
+ @Mock private KeyguardMessageArea mKeyguardMessageArea;
+ @Mock private ShadeController mShadeController;
+ @Mock private SysUIUnfoldComponent mSysUiUnfoldComponent;
+ @Mock private DreamOverlayStateController mDreamOverlayStateController;
+ @Mock private LatencyTracker mLatencyTracker;
+ @Mock private FeatureFlags mFeatureFlags;
+ @Mock private KeyguardSecurityModel mKeyguardSecurityModel;
+ @Mock private PrimaryBouncerCallbackInteractor mPrimaryBouncerCallbackInteractor;
+ @Mock private PrimaryBouncerInteractor mPrimaryBouncerInteractor;
+ @Mock private BouncerView mBouncerView;
+ @Mock private BouncerViewDelegate mBouncerViewDelegate;
+
+ private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+ private KeyguardBouncer.PrimaryBouncerExpansionCallback mBouncerExpansionCallback;
+ private FakeKeyguardStateController mKeyguardStateController =
+ spy(new FakeKeyguardStateController());
+
+ @Mock private ViewRootImpl mViewRootImpl;
+ @Mock private WindowOnBackInvokedDispatcher mOnBackInvokedDispatcher;
+ @Captor
+ private ArgumentCaptor<OnBackInvokedCallback> mOnBackInvokedCallback;
+
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ when(mKeyguardBouncerFactory.create(
+ any(ViewGroup.class),
+ any(KeyguardBouncer.PrimaryBouncerExpansionCallback.class)))
+ .thenReturn(mPrimaryBouncer);
+ when(mCentralSurfaces.getBouncerContainer()).thenReturn(mContainer);
+ when(mContainer.findViewById(anyInt())).thenReturn(mKeyguardMessageArea);
+ when(mKeyguardMessageAreaFactory.create(any(KeyguardMessageArea.class)))
+ .thenReturn(mKeyguardMessageAreaController);
+ when(mBouncerView.getDelegate()).thenReturn(mBouncerViewDelegate);
+
+ mStatusBarKeyguardViewManager =
+ new StatusBarKeyguardViewManager(
+ getContext(),
+ mViewMediatorCallback,
+ mLockPatternUtils,
+ mStatusBarStateController,
+ mock(ConfigurationController.class),
+ mKeyguardUpdateMonitor,
+ mDreamOverlayStateController,
+ mock(NavigationModeController.class),
+ mock(DockManager.class),
+ mock(NotificationShadeWindowController.class),
+ mKeyguardStateController,
+ mock(NotificationMediaManager.class),
+ mKeyguardBouncerFactory,
+ mKeyguardMessageAreaFactory,
+ Optional.of(mSysUiUnfoldComponent),
+ () -> mShadeController,
+ mLatencyTracker,
+ mKeyguardSecurityModel,
+ mFeatureFlags,
+ mPrimaryBouncerCallbackInteractor,
+ mPrimaryBouncerInteractor,
+ mBouncerView) {
+ @Override
+ public ViewRootImpl getViewRootImpl() {
+ return mViewRootImpl;
+ }
+ };
+ when(mViewRootImpl.getOnBackInvokedDispatcher())
+ .thenReturn(mOnBackInvokedDispatcher);
+ mStatusBarKeyguardViewManager.registerCentralSurfaces(
+ mCentralSurfaces,
+ mNotificationPanelView,
+ new ShadeExpansionStateManager(),
+ mBiometricUnlockController,
+ mNotificationContainer,
+ mBypassController);
+ mStatusBarKeyguardViewManager.show(null);
+ ArgumentCaptor<KeyguardBouncer.PrimaryBouncerExpansionCallback> callbackArgumentCaptor =
+ ArgumentCaptor.forClass(KeyguardBouncer.PrimaryBouncerExpansionCallback.class);
+ verify(mKeyguardBouncerFactory).create(any(ViewGroup.class),
+ callbackArgumentCaptor.capture());
+ mBouncerExpansionCallback = callbackArgumentCaptor.getValue();
+ }
+
+ @Test
+ public void dismissWithAction_AfterKeyguardGoneSetToFalse() {
+ OnDismissAction action = () -> false;
+ Runnable cancelAction = () -> {};
+ mStatusBarKeyguardViewManager.dismissWithAction(
+ action, cancelAction, false /* afterKeyguardGone */);
+ verify(mPrimaryBouncer).showWithDismissAction(eq(action), eq(cancelAction));
+ }
+
+ @Test
+ public void showBouncer_onlyWhenShowing() {
+ mStatusBarKeyguardViewManager.hide(0 /* startTime */, 0 /* fadeoutDuration */);
+ mStatusBarKeyguardViewManager.showPrimaryBouncer(true /* scrimmed */);
+ verify(mPrimaryBouncer, never()).show(anyBoolean(), anyBoolean());
+ verify(mPrimaryBouncer, never()).show(anyBoolean());
+ }
+
+ @Test
+ public void showBouncer_notWhenBouncerAlreadyShowing() {
+ mStatusBarKeyguardViewManager.hide(0 /* startTime */, 0 /* fadeoutDuration */);
+ when(mPrimaryBouncer.isSecure()).thenReturn(true);
+ mStatusBarKeyguardViewManager.showPrimaryBouncer(true /* scrimmed */);
+ verify(mPrimaryBouncer, never()).show(anyBoolean(), anyBoolean());
+ verify(mPrimaryBouncer, never()).show(anyBoolean());
+ }
+
+ @Test
+ public void showBouncer_showsTheBouncer() {
+ mStatusBarKeyguardViewManager.showPrimaryBouncer(true /* scrimmed */);
+ verify(mPrimaryBouncer).show(anyBoolean(), eq(true));
+ }
+
+ @Test
+ public void onPanelExpansionChanged_neverShowsDuringHintAnimation() {
+ when(mNotificationPanelView.isUnlockHintRunning()).thenReturn(true);
+ mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
+ verify(mPrimaryBouncer, never()).setExpansion(anyFloat());
+ }
+
+ @Test
+ public void onPanelExpansionChanged_propagatesToBouncerOnlyIfShowing() {
+ mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
+ verify(mPrimaryBouncer, never()).setExpansion(eq(0.5f));
+
+ when(mPrimaryBouncer.isShowing()).thenReturn(true);
+ mStatusBarKeyguardViewManager.onPanelExpansionChanged(
+ expansionEvent(/* fraction= */ 0.6f, /* expanded= */ false, /* tracking= */ true));
+ verify(mPrimaryBouncer).setExpansion(eq(0.6f));
+ }
+
+ @Test
+ public void onPanelExpansionChanged_duplicateEventsAreIgnored() {
+ when(mPrimaryBouncer.isShowing()).thenReturn(true);
+ mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
+ verify(mPrimaryBouncer).setExpansion(eq(0.5f));
+
+ reset(mPrimaryBouncer);
+ mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
+ verify(mPrimaryBouncer, never()).setExpansion(eq(0.5f));
+ }
+
+ @Test
+ public void onPanelExpansionChanged_hideBouncer_afterKeyguardHidden() {
+ mStatusBarKeyguardViewManager.hide(0, 0);
+ when(mPrimaryBouncer.inTransit()).thenReturn(true);
+
+ mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
+ verify(mPrimaryBouncer).setExpansion(eq(KeyguardBouncer.EXPANSION_HIDDEN));
+ }
+
+ @Test
+ public void onPanelExpansionChanged_showsBouncerWhenSwiping() {
+ mKeyguardStateController.setCanDismissLockScreen(false);
+ mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
+ verify(mPrimaryBouncer).show(eq(false), eq(false));
+
+ // But not when it's already visible
+ reset(mPrimaryBouncer);
+ when(mPrimaryBouncer.isShowing()).thenReturn(true);
+ mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
+ verify(mPrimaryBouncer, never()).show(eq(false), eq(false));
+
+ // Or animating away
+ reset(mPrimaryBouncer);
+ when(mPrimaryBouncer.isAnimatingAway()).thenReturn(true);
+ mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
+ verify(mPrimaryBouncer, never()).show(eq(false), eq(false));
+ }
+
+ @Test
+ public void onPanelExpansionChanged_neverTranslatesBouncerWhenWakeAndUnlock() {
+ when(mBiometricUnlockController.getMode())
+ .thenReturn(BiometricUnlockController.MODE_WAKE_AND_UNLOCK);
+ mStatusBarKeyguardViewManager.onPanelExpansionChanged(
+ expansionEvent(
+ /* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE,
+ /* expanded= */ true,
+ /* tracking= */ false));
+ verify(mPrimaryBouncer, never()).setExpansion(anyFloat());
+ }
+
+ @Test
+ public void onPanelExpansionChanged_neverTranslatesBouncerWhenDismissBouncer() {
+ // Since KeyguardBouncer.EXPANSION_VISIBLE = 0 panel expansion, if the unlock is dismissing
+ // the bouncer, there may be an onPanelExpansionChanged(0) call to collapse the panel
+ // which would mistakenly cause the bouncer to show briefly before its visibility
+ // is set to hide. Therefore, we don't want to propagate panelExpansionChanged to the
+ // bouncer if the bouncer is dismissing as a result of a biometric unlock.
+ when(mBiometricUnlockController.getMode())
+ .thenReturn(BiometricUnlockController.MODE_DISMISS_BOUNCER);
+ mStatusBarKeyguardViewManager.onPanelExpansionChanged(
+ expansionEvent(
+ /* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE,
+ /* expanded= */ true,
+ /* tracking= */ false));
+ verify(mPrimaryBouncer, never()).setExpansion(anyFloat());
+ }
+
+ @Test
+ public void onPanelExpansionChanged_neverTranslatesBouncerWhenOccluded() {
+ when(mKeyguardStateController.isOccluded()).thenReturn(true);
+ mStatusBarKeyguardViewManager.onPanelExpansionChanged(
+ expansionEvent(
+ /* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE,
+ /* expanded= */ true,
+ /* tracking= */ false));
+ verify(mPrimaryBouncer, never()).setExpansion(anyFloat());
+ }
+
+ @Test
+ public void onPanelExpansionChanged_neverTranslatesBouncerWhenShowBouncer() {
+ // Since KeyguardBouncer.EXPANSION_VISIBLE = 0 panel expansion, if the unlock is dismissing
+ // the bouncer, there may be an onPanelExpansionChanged(0) call to collapse the panel
+ // which would mistakenly cause the bouncer to show briefly before its visibility
+ // is set to hide. Therefore, we don't want to propagate panelExpansionChanged to the
+ // bouncer if the bouncer is dismissing as a result of a biometric unlock.
+ when(mBiometricUnlockController.getMode())
+ .thenReturn(BiometricUnlockController.MODE_SHOW_BOUNCER);
+ mStatusBarKeyguardViewManager.onPanelExpansionChanged(
+ expansionEvent(
+ /* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE,
+ /* expanded= */ true,
+ /* tracking= */ false));
+ verify(mPrimaryBouncer, never()).setExpansion(anyFloat());
+ }
+
+ @Test
+ public void onPanelExpansionChanged_neverTranslatesBouncerWhenShadeLocked() {
+ when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE_LOCKED);
+ mStatusBarKeyguardViewManager.onPanelExpansionChanged(
+ expansionEvent(
+ /* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE,
+ /* expanded= */ true,
+ /* tracking= */ false));
+ verify(mPrimaryBouncer, never()).setExpansion(anyFloat());
+ }
+
+ @Test
+ public void setOccluded_animatesPanelExpansion_onlyIfBouncerHidden() {
+ mStatusBarKeyguardViewManager.setOccluded(false /* occluded */, true /* animated */);
+ verify(mCentralSurfaces).animateKeyguardUnoccluding();
+
+ when(mPrimaryBouncer.isShowing()).thenReturn(true);
+ clearInvocations(mCentralSurfaces);
+ mStatusBarKeyguardViewManager.setOccluded(false /* occluded */, true /* animated */);
+ verify(mCentralSurfaces, never()).animateKeyguardUnoccluding();
+ }
+
+ @Test
+ public void setOccluded_onKeyguardOccludedChangedCalled() {
+ clearInvocations(mKeyguardStateController);
+ clearInvocations(mKeyguardUpdateMonitor);
+
+ mStatusBarKeyguardViewManager.setOccluded(false /* occluded */, false /* animated */);
+ verify(mKeyguardStateController).notifyKeyguardState(true, false);
+
+ clearInvocations(mKeyguardUpdateMonitor);
+ clearInvocations(mKeyguardStateController);
+
+ mStatusBarKeyguardViewManager.setOccluded(true /* occluded */, false /* animated */);
+ verify(mKeyguardStateController).notifyKeyguardState(true, true);
+
+ clearInvocations(mKeyguardUpdateMonitor);
+ clearInvocations(mKeyguardStateController);
+
+ mStatusBarKeyguardViewManager.setOccluded(false /* occluded */, false /* animated */);
+ verify(mKeyguardStateController).notifyKeyguardState(true, false);
+ }
+
+ @Test
+ public void setOccluded_isInLaunchTransition_onKeyguardOccludedChangedCalled() {
+ mStatusBarKeyguardViewManager.show(null);
+
+ mStatusBarKeyguardViewManager.setOccluded(true /* occluded */, false /* animated */);
+ verify(mKeyguardStateController).notifyKeyguardState(true, true);
+ }
+
+ @Test
+ public void setOccluded_isLaunchingActivityOverLockscreen_onKeyguardOccludedChangedCalled() {
+ when(mCentralSurfaces.isLaunchingActivityOverLockscreen()).thenReturn(true);
+ mStatusBarKeyguardViewManager.show(null);
+
+ mStatusBarKeyguardViewManager.setOccluded(true /* occluded */, false /* animated */);
+ verify(mKeyguardStateController).notifyKeyguardState(true, true);
+ }
+
+ @Test
+ public void testHiding_cancelsGoneRunnable() {
+ OnDismissAction action = mock(OnDismissAction.class);
+ Runnable cancelAction = mock(Runnable.class);
+ mStatusBarKeyguardViewManager.dismissWithAction(
+ action, cancelAction, true /* afterKeyguardGone */);
+
+ when(mPrimaryBouncer.isShowing()).thenReturn(false);
+ mStatusBarKeyguardViewManager.hideBouncer(true);
+ mStatusBarKeyguardViewManager.hide(0, 30);
+ verify(action, never()).onDismiss();
+ verify(cancelAction).run();
+ }
+
+ @Test
+ public void testHidingBouncer_cancelsGoneRunnable() {
+ OnDismissAction action = mock(OnDismissAction.class);
+ Runnable cancelAction = mock(Runnable.class);
+ mStatusBarKeyguardViewManager.dismissWithAction(
+ action, cancelAction, true /* afterKeyguardGone */);
+
+ when(mPrimaryBouncer.isShowing()).thenReturn(false);
+ mStatusBarKeyguardViewManager.hideBouncer(true);
+
+ verify(action, never()).onDismiss();
+ verify(cancelAction).run();
+ }
+
+ @Test
+ public void testHiding_doesntCancelWhenShowing() {
+ OnDismissAction action = mock(OnDismissAction.class);
+ Runnable cancelAction = mock(Runnable.class);
+ mStatusBarKeyguardViewManager.dismissWithAction(
+ action, cancelAction, true /* afterKeyguardGone */);
+
+ mStatusBarKeyguardViewManager.hide(0, 30);
+ verify(action).onDismiss();
+ verify(cancelAction, never()).run();
+ }
+
+ @Test
+ public void testShowing_whenAlternateAuthShowing() {
+ mStatusBarKeyguardViewManager.setAlternateBouncer(mAlternateBouncer);
+ when(mPrimaryBouncer.isShowing()).thenReturn(false);
+ when(mAlternateBouncer.isShowingAlternateBouncer()).thenReturn(true);
+ assertTrue(
+ "Is showing not accurate when alternative auth showing",
+ mStatusBarKeyguardViewManager.isBouncerShowing());
+ }
+
+ @Test
+ public void testWillBeShowing_whenAlternateAuthShowing() {
+ mStatusBarKeyguardViewManager.setAlternateBouncer(mAlternateBouncer);
+ when(mPrimaryBouncer.isShowing()).thenReturn(false);
+ when(mAlternateBouncer.isShowingAlternateBouncer()).thenReturn(true);
+ assertTrue(
+ "Is or will be showing not accurate when alternative auth showing",
+ mStatusBarKeyguardViewManager.primaryBouncerIsOrWillBeShowing());
+ }
+
+ @Test
+ public void testHideAlternateBouncer_onShowBouncer() {
+ // GIVEN alt auth is showing
+ mStatusBarKeyguardViewManager.setAlternateBouncer(mAlternateBouncer);
+ when(mPrimaryBouncer.isShowing()).thenReturn(false);
+ when(mAlternateBouncer.isShowingAlternateBouncer()).thenReturn(true);
+ reset(mAlternateBouncer);
+
+ // WHEN showBouncer is called
+ mStatusBarKeyguardViewManager.showPrimaryBouncer(true);
+
+ // THEN alt bouncer should be hidden
+ verify(mAlternateBouncer).hideAlternateBouncer();
+ }
+
+ @Test
+ public void testBouncerIsOrWillBeShowing_whenBouncerIsInTransit() {
+ when(mPrimaryBouncer.isShowing()).thenReturn(false);
+ when(mPrimaryBouncer.inTransit()).thenReturn(true);
+
+ assertTrue(
+ "Is or will be showing should be true when bouncer is in transit",
+ mStatusBarKeyguardViewManager.primaryBouncerIsOrWillBeShowing());
+ }
+
+ @Test
+ public void testShowAltAuth_unlockingWithBiometricNotAllowed() {
+ // GIVEN alt auth exists, unlocking with biometric isn't allowed
+ mStatusBarKeyguardViewManager.setAlternateBouncer(mAlternateBouncer);
+ when(mPrimaryBouncer.isShowing()).thenReturn(false);
+ when(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean()))
+ .thenReturn(false);
+
+ // WHEN showGenericBouncer is called
+ final boolean scrimmed = true;
+ mStatusBarKeyguardViewManager.showBouncer(scrimmed);
+
+ // THEN regular bouncer is shown
+ verify(mPrimaryBouncer).show(anyBoolean(), eq(scrimmed));
+ verify(mAlternateBouncer, never()).showAlternateBouncer();
+ }
+
+ @Test
+ public void testShowAlternateBouncer_unlockingWithBiometricAllowed() {
+ // GIVEN alt auth exists, unlocking with biometric is allowed
+ mStatusBarKeyguardViewManager.setAlternateBouncer(mAlternateBouncer);
+ when(mPrimaryBouncer.isShowing()).thenReturn(false);
+ when(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
+
+ // WHEN showGenericBouncer is called
+ mStatusBarKeyguardViewManager.showBouncer(true);
+
+ // THEN alt auth bouncer is shown
+ verify(mAlternateBouncer).showAlternateBouncer();
+ verify(mPrimaryBouncer, never()).show(anyBoolean(), anyBoolean());
+ }
+
+ @Test
+ public void testUpdateResources_delegatesToBouncer() {
+ mStatusBarKeyguardViewManager.updateResources();
+
+ verify(mPrimaryBouncer).updateResources();
+ }
+
+ @Test
+ public void updateKeyguardPosition_delegatesToBouncer() {
+ mStatusBarKeyguardViewManager.updateKeyguardPosition(1.0f);
+
+ verify(mPrimaryBouncer).updateKeyguardPosition(1.0f);
+ }
+
+ @Test
+ public void testIsBouncerInTransit() {
+ when(mPrimaryBouncer.inTransit()).thenReturn(true);
+ Truth.assertThat(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).isTrue();
+ when(mPrimaryBouncer.inTransit()).thenReturn(false);
+ Truth.assertThat(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).isFalse();
+ mPrimaryBouncer = null;
+ Truth.assertThat(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).isFalse();
+ }
+
+ private static ShadeExpansionChangeEvent expansionEvent(
+ float fraction, boolean expanded, boolean tracking) {
+ return new ShadeExpansionChangeEvent(
+ fraction, expanded, tracking, /* dragDownPxAmount= */ 0f);
+ }
+
+ @Test
+ public void testPredictiveBackCallback_registration() {
+ /* verify that a predictive back callback is registered when the bouncer becomes visible */
+ mBouncerExpansionCallback.onVisibilityChanged(true);
+ verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback(
+ eq(OnBackInvokedDispatcher.PRIORITY_OVERLAY),
+ mOnBackInvokedCallback.capture());
+
+ /* verify that the same callback is unregistered when the bouncer becomes invisible */
+ mBouncerExpansionCallback.onVisibilityChanged(false);
+ verify(mOnBackInvokedDispatcher).unregisterOnBackInvokedCallback(
+ eq(mOnBackInvokedCallback.getValue()));
+ }
+
+ @Test
+ public void testPredictiveBackCallback_invocationHidesBouncer() {
+ mBouncerExpansionCallback.onVisibilityChanged(true);
+ /* capture the predictive back callback during registration */
+ verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback(
+ eq(OnBackInvokedDispatcher.PRIORITY_OVERLAY),
+ mOnBackInvokedCallback.capture());
+
+ when(mPrimaryBouncer.isShowing()).thenReturn(true);
+ when(mCentralSurfaces.shouldKeyguardHideImmediately()).thenReturn(true);
+ /* invoke the back callback directly */
+ mOnBackInvokedCallback.getValue().onBackInvoked();
+
+ /* verify that the bouncer will be hidden as a result of the invocation */
+ verify(mCentralSurfaces).setBouncerShowing(eq(false));
+ }
+
+ @Test
+ public void testReportBouncerOnDreamWhenVisible() {
+ mBouncerExpansionCallback.onVisibilityChanged(true);
+ verify(mCentralSurfaces).setBouncerShowingOverDream(false);
+ Mockito.clearInvocations(mCentralSurfaces);
+ when(mDreamOverlayStateController.isOverlayActive()).thenReturn(true);
+ mBouncerExpansionCallback.onVisibilityChanged(true);
+ verify(mCentralSurfaces).setBouncerShowingOverDream(true);
+ }
+
+ @Test
+ public void testReportBouncerOnDreamWhenNotVisible() {
+ mBouncerExpansionCallback.onVisibilityChanged(false);
+ verify(mCentralSurfaces).setBouncerShowingOverDream(false);
+ Mockito.clearInvocations(mCentralSurfaces);
+ when(mDreamOverlayStateController.isOverlayActive()).thenReturn(true);
+ mBouncerExpansionCallback.onVisibilityChanged(false);
+ verify(mCentralSurfaces).setBouncerShowingOverDream(false);
+ }
+
+ @Test
+ public void flag_off_DoesNotCallBouncerInteractor() {
+ when(mFeatureFlags.isEnabled(MODERN_BOUNCER)).thenReturn(false);
+ mStatusBarKeyguardViewManager.hideBouncer(false);
+ verify(mPrimaryBouncerInteractor, never()).hide();
+ }
+
+ @Test
+ public void hideAlternateBouncer_beforeCentralSurfacesRegistered() {
+ mStatusBarKeyguardViewManager =
+ new StatusBarKeyguardViewManager(
+ getContext(),
+ mViewMediatorCallback,
+ mLockPatternUtils,
+ mStatusBarStateController,
+ mock(ConfigurationController.class),
+ mKeyguardUpdateMonitor,
+ mDreamOverlayStateController,
+ mock(NavigationModeController.class),
+ mock(DockManager.class),
+ mock(NotificationShadeWindowController.class),
+ mKeyguardStateController,
+ mock(NotificationMediaManager.class),
+ mKeyguardBouncerFactory,
+ mKeyguardMessageAreaFactory,
+ Optional.of(mSysUiUnfoldComponent),
+ () -> mShadeController,
+ mLatencyTracker,
+ mKeyguardSecurityModel,
+ mFeatureFlags,
+ mPrimaryBouncerCallbackInteractor,
+ mPrimaryBouncerInteractor,
+ mBouncerView) {
+ @Override
+ public ViewRootImpl getViewRootImpl() {
+ return mViewRootImpl;
+ }
+ };
+
+ // the following call before registering centralSurfaces should NOT throw a NPE:
+ mStatusBarKeyguardViewManager.hideAlternateBouncer(true);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
index 288f54c7d03c..5265ec66bc24 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
@@ -16,12 +16,13 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
import kotlinx.coroutines.flow.MutableStateFlow
-class FakeMobileConnectionRepository : MobileConnectionRepository {
- private val _subscriptionsModelFlow = MutableStateFlow(MobileSubscriptionModel())
- override val subscriptionModelFlow = _subscriptionsModelFlow
+// TODO(b/261632894): remove this in favor of the real impl or DemoMobileConnectionRepository
+class FakeMobileConnectionRepository(override val subId: Int) : MobileConnectionRepository {
+ private val _connectionInfo = MutableStateFlow(MobileConnectionModel())
+ override val connectionInfo = _connectionInfo
private val _dataEnabled = MutableStateFlow(true)
override val dataEnabled = _dataEnabled
@@ -29,8 +30,8 @@ class FakeMobileConnectionRepository : MobileConnectionRepository {
private val _isDefaultDataSubscription = MutableStateFlow(true)
override val isDefaultDataSubscription = _isDefaultDataSubscription
- fun setMobileSubscriptionModel(model: MobileSubscriptionModel) {
- _subscriptionsModelFlow.value = model
+ fun setConnectionInfo(model: MobileConnectionModel) {
+ _connectionInfo.value = model
}
fun setDataEnabled(enabled: Boolean) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
index 533d5d9d5b4a..d6af0e6b3a82 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
@@ -16,23 +16,43 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository
-import android.telephony.SubscriptionInfo
import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
-import com.android.settingslib.mobile.MobileMappings.Config
+import android.telephony.TelephonyDisplayInfo
+import android.telephony.TelephonyManager
+import com.android.settingslib.SignalIcon
+import com.android.settingslib.mobile.TelephonyIcons
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
-import kotlinx.coroutines.flow.Flow
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
+import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
import kotlinx.coroutines.flow.MutableStateFlow
-class FakeMobileConnectionsRepository : MobileConnectionsRepository {
- private val _subscriptionsFlow = MutableStateFlow<List<SubscriptionInfo>>(listOf())
- override val subscriptionsFlow: Flow<List<SubscriptionInfo>> = _subscriptionsFlow
+// TODO(b/261632894): remove this in favor of the real impl or DemoMobileConnectionsRepository
+class FakeMobileConnectionsRepository(mobileMappings: MobileMappingsProxy) :
+ MobileConnectionsRepository {
+ val GSM_KEY = mobileMappings.toIconKey(GSM)
+ val LTE_KEY = mobileMappings.toIconKey(LTE)
+ val UMTS_KEY = mobileMappings.toIconKey(UMTS)
+ val LTE_ADVANCED_KEY = mobileMappings.toIconKeyOverride(LTE_ADVANCED_PRO)
+
+ /**
+ * To avoid a reliance on [MobileMappings], we'll build a simpler map from network type to
+ * mobile icon. See TelephonyManager.NETWORK_TYPES for a list of types and [TelephonyIcons] for
+ * the exhaustive set of icons
+ */
+ val TEST_MAPPING: Map<String, SignalIcon.MobileIconGroup> =
+ mapOf(
+ GSM_KEY to TelephonyIcons.THREE_G,
+ LTE_KEY to TelephonyIcons.LTE,
+ UMTS_KEY to TelephonyIcons.FOUR_G,
+ LTE_ADVANCED_KEY to TelephonyIcons.NR_5G,
+ )
+
+ private val _subscriptions = MutableStateFlow<List<SubscriptionModel>>(listOf())
+ override val subscriptions = _subscriptions
private val _activeMobileDataSubscriptionId = MutableStateFlow(INVALID_SUBSCRIPTION_ID)
override val activeMobileDataSubscriptionId = _activeMobileDataSubscriptionId
- private val _defaultDataSubRatConfig = MutableStateFlow(Config())
- override val defaultDataSubRatConfig = _defaultDataSubRatConfig
-
private val _defaultDataSubId = MutableStateFlow(INVALID_SUBSCRIPTION_ID)
override val defaultDataSubId = _defaultDataSubId
@@ -41,18 +61,21 @@ class FakeMobileConnectionsRepository : MobileConnectionsRepository {
private val subIdRepos = mutableMapOf<Int, MobileConnectionRepository>()
override fun getRepoForSubId(subId: Int): MobileConnectionRepository {
- return subIdRepos[subId] ?: FakeMobileConnectionRepository().also { subIdRepos[subId] = it }
+ return subIdRepos[subId]
+ ?: FakeMobileConnectionRepository(subId).also { subIdRepos[subId] = it }
}
private val _globalMobileDataSettingChangedEvent = MutableStateFlow(Unit)
override val globalMobileDataSettingChangedEvent = _globalMobileDataSettingChangedEvent
- fun setSubscriptions(subs: List<SubscriptionInfo>) {
- _subscriptionsFlow.value = subs
- }
+ private val _defaultMobileIconMapping = MutableStateFlow(TEST_MAPPING)
+ override val defaultMobileIconMapping = _defaultMobileIconMapping
+
+ private val _defaultMobileIconGroup = MutableStateFlow(DEFAULT_ICON)
+ override val defaultMobileIconGroup = _defaultMobileIconGroup
- fun setDefaultDataSubRatConfig(config: Config) {
- _defaultDataSubRatConfig.value = config
+ fun setSubscriptions(subs: List<SubscriptionModel>) {
+ _subscriptions.value = subs
}
fun setDefaultDataSubId(id: Int) {
@@ -74,4 +97,14 @@ class FakeMobileConnectionsRepository : MobileConnectionsRepository {
fun setMobileConnectionRepositoryMap(connections: Map<Int, MobileConnectionRepository>) {
connections.forEach { entry -> subIdRepos[entry.key] = entry.value }
}
+
+ companion object {
+ val DEFAULT_ICON = TelephonyIcons.G
+
+ // Use [MobileMappings] to define some simple definitions
+ const val GSM = TelephonyManager.NETWORK_TYPE_GSM
+ const val LTE = TelephonyManager.NETWORK_TYPE_LTE
+ const val UMTS = TelephonyManager.NETWORK_TYPE_UMTS
+ const val LTE_ADVANCED_PRO = TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt
new file mode 100644
index 000000000000..18ae90db881a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.data.repository
+
+import android.net.ConnectivityManager
+import android.telephony.SubscriptionInfo
+import android.telephony.SubscriptionManager
+import android.telephony.TelephonyManager
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.demomode.DemoMode
+import com.android.systemui.demomode.DemoModeController
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.DemoMobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.DemoModeMobileConnectionDataSource
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.validMobileEvent
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileConnectionsRepositoryImpl
+import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.kotlinArgumentCaptor
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.settings.FakeSettings
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+/**
+ * The switcher acts as a dispatcher to either the `prod` or `demo` versions of the repository
+ * interface it's switching on. These tests just need to verify that the entire interface properly
+ * switches over when the value of `demoMode` changes
+ */
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class MobileRepositorySwitcherTest : SysuiTestCase() {
+ private lateinit var underTest: MobileRepositorySwitcher
+ private lateinit var realRepo: MobileConnectionsRepositoryImpl
+ private lateinit var demoRepo: DemoMobileConnectionsRepository
+ private lateinit var mockDataSource: DemoModeMobileConnectionDataSource
+
+ @Mock private lateinit var connectivityManager: ConnectivityManager
+ @Mock private lateinit var subscriptionManager: SubscriptionManager
+ @Mock private lateinit var telephonyManager: TelephonyManager
+ @Mock private lateinit var logger: ConnectivityPipelineLogger
+ @Mock private lateinit var demoModeController: DemoModeController
+
+ private val globalSettings = FakeSettings()
+ private val fakeNetworkEventsFlow = MutableStateFlow<FakeNetworkEventModel?>(null)
+ private val mobileMappings = FakeMobileMappingsProxy()
+
+ private val scope = CoroutineScope(IMMEDIATE)
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ // Never start in demo mode
+ whenever(demoModeController.isInDemoMode).thenReturn(false)
+
+ mockDataSource =
+ mock<DemoModeMobileConnectionDataSource>().also {
+ whenever(it.mobileEvents).thenReturn(fakeNetworkEventsFlow)
+ }
+
+ realRepo =
+ MobileConnectionsRepositoryImpl(
+ connectivityManager,
+ subscriptionManager,
+ telephonyManager,
+ logger,
+ mobileMappings,
+ fakeBroadcastDispatcher,
+ globalSettings,
+ context,
+ IMMEDIATE,
+ scope,
+ mock(),
+ )
+
+ demoRepo =
+ DemoMobileConnectionsRepository(
+ dataSource = mockDataSource,
+ scope = scope,
+ context = context,
+ )
+
+ underTest =
+ MobileRepositorySwitcher(
+ scope = scope,
+ realRepository = realRepo,
+ demoMobileConnectionsRepository = demoRepo,
+ demoModeController = demoModeController,
+ )
+ }
+
+ @After
+ fun tearDown() {
+ scope.cancel()
+ }
+
+ @Test
+ fun `active repo matches demo mode setting`() =
+ runBlocking(IMMEDIATE) {
+ whenever(demoModeController.isInDemoMode).thenReturn(false)
+
+ var latest: MobileConnectionsRepository? = null
+ val job = underTest.activeRepo.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(realRepo)
+
+ startDemoMode()
+
+ assertThat(latest).isEqualTo(demoRepo)
+
+ finishDemoMode()
+
+ assertThat(latest).isEqualTo(realRepo)
+
+ job.cancel()
+ }
+
+ @Test
+ fun `subscription list updates when demo mode changes`() =
+ runBlocking(IMMEDIATE) {
+ whenever(demoModeController.isInDemoMode).thenReturn(false)
+
+ whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+ .thenReturn(listOf(SUB_1, SUB_2))
+
+ var latest: List<SubscriptionModel>? = null
+ val job = underTest.subscriptions.onEach { latest = it }.launchIn(this)
+
+ // The real subscriptions has 2 subs
+ whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+ .thenReturn(listOf(SUB_1, SUB_2))
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ assertThat(latest).isEqualTo(listOf(MODEL_1, MODEL_2))
+
+ // Demo mode turns on, and we should see only the demo subscriptions
+ startDemoMode()
+ fakeNetworkEventsFlow.value = validMobileEvent(subId = 3)
+
+ // Demo mobile connections repository makes arbitrarily-formed subscription info
+ // objects, so just validate the data we care about
+ assertThat(latest).hasSize(1)
+ assertThat(latest!![0].subscriptionId).isEqualTo(3)
+
+ finishDemoMode()
+
+ assertThat(latest).isEqualTo(listOf(MODEL_1, MODEL_2))
+
+ job.cancel()
+ }
+
+ private fun startDemoMode() {
+ whenever(demoModeController.isInDemoMode).thenReturn(true)
+ getDemoModeCallback().onDemoModeStarted()
+ }
+
+ private fun finishDemoMode() {
+ whenever(demoModeController.isInDemoMode).thenReturn(false)
+ getDemoModeCallback().onDemoModeFinished()
+ }
+
+ private fun getSubscriptionCallback(): SubscriptionManager.OnSubscriptionsChangedListener {
+ val callbackCaptor =
+ kotlinArgumentCaptor<SubscriptionManager.OnSubscriptionsChangedListener>()
+ verify(subscriptionManager)
+ .addOnSubscriptionsChangedListener(any(), callbackCaptor.capture())
+ return callbackCaptor.value
+ }
+
+ private fun getDemoModeCallback(): DemoMode {
+ val captor = kotlinArgumentCaptor<DemoMode>()
+ verify(demoModeController).addCallback(captor.capture())
+ return captor.value
+ }
+
+ companion object {
+ private val IMMEDIATE = Dispatchers.Main.immediate
+
+ private const val SUB_1_ID = 1
+ private val SUB_1 =
+ mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_1_ID) }
+ private val MODEL_1 = SubscriptionModel(subscriptionId = SUB_1_ID)
+
+ private const val SUB_2_ID = 2
+ private val SUB_2 =
+ mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_2_ID) }
+ private val MODEL_2 = SubscriptionModel(subscriptionId = SUB_2_ID)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt
new file mode 100644
index 000000000000..e943de25c15f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt
@@ -0,0 +1,249 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.data.repository.demo
+
+import android.telephony.Annotation
+import android.telephony.TelephonyManager
+import androidx.test.filters.SmallTest
+import com.android.settingslib.SignalIcon
+import com.android.settingslib.mobile.TelephonyIcons
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.junit.runners.Parameterized.Parameters
+
+/**
+ * Parameterized test for all of the common values of [FakeNetworkEventModel]. This test simply
+ * verifies that passing the given model to [DemoMobileConnectionsRepository] results in the correct
+ * flows emitting from the given connection.
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(Parameterized::class)
+internal class DemoMobileConnectionParameterizedTest(private val testCase: TestCase) :
+ SysuiTestCase() {
+ private val testDispatcher = UnconfinedTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
+
+ private val fakeNetworkEventFlow = MutableStateFlow<FakeNetworkEventModel?>(null)
+
+ private lateinit var connectionsRepo: DemoMobileConnectionsRepository
+ private lateinit var underTest: DemoMobileConnectionRepository
+ private lateinit var mockDataSource: DemoModeMobileConnectionDataSource
+
+ @Before
+ fun setUp() {
+ // The data source only provides one API, so we can mock it with a flow here for convenience
+ mockDataSource =
+ mock<DemoModeMobileConnectionDataSource>().also {
+ whenever(it.mobileEvents).thenReturn(fakeNetworkEventFlow)
+ }
+
+ connectionsRepo =
+ DemoMobileConnectionsRepository(
+ dataSource = mockDataSource,
+ scope = testScope.backgroundScope,
+ context = context,
+ )
+
+ connectionsRepo.startProcessingCommands()
+ }
+
+ @After
+ fun tearDown() {
+ testScope.cancel()
+ }
+
+ @Test
+ fun demoNetworkData() =
+ testScope.runTest {
+ val networkModel =
+ FakeNetworkEventModel.Mobile(
+ level = testCase.level,
+ dataType = testCase.dataType,
+ subId = testCase.subId,
+ carrierId = testCase.carrierId,
+ inflateStrength = testCase.inflateStrength,
+ activity = testCase.activity,
+ carrierNetworkChange = testCase.carrierNetworkChange,
+ )
+
+ fakeNetworkEventFlow.value = networkModel
+ underTest = connectionsRepo.getRepoForSubId(subId)
+
+ assertConnection(underTest, networkModel)
+ }
+
+ private fun assertConnection(
+ conn: DemoMobileConnectionRepository,
+ model: FakeNetworkEventModel
+ ) {
+ when (model) {
+ is FakeNetworkEventModel.Mobile -> {
+ val connectionInfo: MobileConnectionModel = conn.connectionInfo.value
+ assertThat(conn.subId).isEqualTo(model.subId)
+ assertThat(connectionInfo.cdmaLevel).isEqualTo(model.level)
+ assertThat(connectionInfo.primaryLevel).isEqualTo(model.level)
+ assertThat(connectionInfo.dataActivityDirection).isEqualTo(model.activity)
+ assertThat(connectionInfo.carrierNetworkChangeActive)
+ .isEqualTo(model.carrierNetworkChange)
+
+ // TODO(b/261029387): check these once we start handling them
+ assertThat(connectionInfo.isEmergencyOnly).isFalse()
+ assertThat(connectionInfo.isGsm).isFalse()
+ assertThat(connectionInfo.dataConnectionState)
+ .isEqualTo(DataConnectionState.Connected)
+ }
+ // MobileDisabled isn't combinatorial in nature, and is tested in
+ // DemoMobileConnectionsRepositoryTest.kt
+ else -> {}
+ }
+ }
+
+ /** Matches [FakeNetworkEventModel] */
+ internal data class TestCase(
+ val level: Int,
+ val dataType: SignalIcon.MobileIconGroup,
+ val subId: Int,
+ val carrierId: Int,
+ val inflateStrength: Boolean,
+ @Annotation.DataActivityType val activity: Int,
+ val carrierNetworkChange: Boolean,
+ ) {
+ override fun toString(): String {
+ return "INPUT(level=$level, " +
+ "dataType=${dataType.name}, " +
+ "subId=$subId, " +
+ "carrierId=$carrierId, " +
+ "inflateStrength=$inflateStrength, " +
+ "activity=$activity, " +
+ "carrierNetworkChange=$carrierNetworkChange)"
+ }
+
+ // Convenience for iterating test data and creating new cases
+ fun modifiedBy(
+ level: Int? = null,
+ dataType: SignalIcon.MobileIconGroup? = null,
+ subId: Int? = null,
+ carrierId: Int? = null,
+ inflateStrength: Boolean? = null,
+ @Annotation.DataActivityType activity: Int? = null,
+ carrierNetworkChange: Boolean? = null,
+ ): TestCase =
+ TestCase(
+ level = level ?: this.level,
+ dataType = dataType ?: this.dataType,
+ subId = subId ?: this.subId,
+ carrierId = carrierId ?: this.carrierId,
+ inflateStrength = inflateStrength ?: this.inflateStrength,
+ activity = activity ?: this.activity,
+ carrierNetworkChange = carrierNetworkChange ?: this.carrierNetworkChange
+ )
+ }
+
+ companion object {
+ private val subId = 1
+
+ private val booleanList = listOf(true, false)
+ private val levels = listOf(0, 1, 2, 3)
+ private val dataTypes =
+ listOf(
+ TelephonyIcons.THREE_G,
+ TelephonyIcons.LTE,
+ TelephonyIcons.FOUR_G,
+ TelephonyIcons.NR_5G,
+ TelephonyIcons.NR_5G_PLUS,
+ )
+ private val carrierIds = listOf(1, 10, 100)
+ private val inflateStrength = booleanList
+ private val activity =
+ listOf(
+ TelephonyManager.DATA_ACTIVITY_NONE,
+ TelephonyManager.DATA_ACTIVITY_IN,
+ TelephonyManager.DATA_ACTIVITY_OUT,
+ TelephonyManager.DATA_ACTIVITY_INOUT
+ )
+ private val carrierNetworkChange = booleanList
+
+ @Parameters(name = "{0}") @JvmStatic fun data() = testData()
+
+ /**
+ * Generate some test data. For the sake of convenience, we'll parameterize only non-null
+ * network event data. So given the lists of test data:
+ * ```
+ * list1 = [1, 2, 3]
+ * list2 = [false, true]
+ * list3 = [a, b, c]
+ * ```
+ * We'll generate test cases for:
+ *
+ * Test (1, false, a) Test (2, false, a) Test (3, false, a) Test (1, true, a) Test (1,
+ * false, b) Test (1, false, c)
+ *
+ * NOTE: this is not a combinatorial product of all of the possible sets of parameters.
+ * Since this test is built to exercise demo mode, the general approach is to define a
+ * fully-formed "base case", and from there to make sure to use every valid parameter once,
+ * by defining the rest of the test cases against the base case. Specific use-cases can be
+ * added to the non-parameterized test, or manually below the generated test cases.
+ */
+ private fun testData(): List<TestCase> {
+ val testSet = mutableSetOf<TestCase>()
+
+ val baseCase =
+ TestCase(
+ levels.first(),
+ dataTypes.first(),
+ subId,
+ carrierIds.first(),
+ inflateStrength.first(),
+ activity.first(),
+ carrierNetworkChange.first()
+ )
+
+ val tail =
+ sequenceOf(
+ levels.map { baseCase.modifiedBy(level = it) },
+ dataTypes.map { baseCase.modifiedBy(dataType = it) },
+ carrierIds.map { baseCase.modifiedBy(carrierId = it) },
+ inflateStrength.map { baseCase.modifiedBy(inflateStrength = it) },
+ activity.map { baseCase.modifiedBy(activity = it) },
+ carrierNetworkChange.map { baseCase.modifiedBy(carrierNetworkChange = it) },
+ )
+ .flatten()
+
+ testSet.add(baseCase)
+ tail.toCollection(testSet)
+
+ return testSet.toList()
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt
new file mode 100644
index 000000000000..32d0410d589d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt
@@ -0,0 +1,325 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.data.repository.demo
+
+import android.telephony.TelephonyManager.DATA_ACTIVITY_INOUT
+import android.telephony.TelephonyManager.UNKNOWN_CARRIER_ID
+import androidx.test.filters.SmallTest
+import com.android.settingslib.SignalIcon
+import com.android.settingslib.mobile.TelephonyIcons.THREE_G
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel.MobileDisabled
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import junit.framework.Assert
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+class DemoMobileConnectionsRepositoryTest : SysuiTestCase() {
+ private val testDispatcher = UnconfinedTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
+
+ private val fakeNetworkEventFlow = MutableStateFlow<FakeNetworkEventModel?>(null)
+
+ private lateinit var underTest: DemoMobileConnectionsRepository
+ private lateinit var mockDataSource: DemoModeMobileConnectionDataSource
+
+ @Before
+ fun setUp() {
+ // The data source only provides one API, so we can mock it with a flow here for convenience
+ mockDataSource =
+ mock<DemoModeMobileConnectionDataSource>().also {
+ whenever(it.mobileEvents).thenReturn(fakeNetworkEventFlow)
+ }
+
+ underTest =
+ DemoMobileConnectionsRepository(
+ dataSource = mockDataSource,
+ scope = testScope.backgroundScope,
+ context = context,
+ )
+
+ underTest.startProcessingCommands()
+ }
+
+ @Test
+ fun `network event - create new subscription`() =
+ testScope.runTest {
+ var latest: List<SubscriptionModel>? = null
+ val job = underTest.subscriptions.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEmpty()
+
+ fakeNetworkEventFlow.value = validMobileEvent(subId = 1)
+
+ assertThat(latest).hasSize(1)
+ assertThat(latest!![0].subscriptionId).isEqualTo(1)
+
+ job.cancel()
+ }
+
+ @Test
+ fun `network event - reuses subscription when same Id`() =
+ testScope.runTest {
+ var latest: List<SubscriptionModel>? = null
+ val job = underTest.subscriptions.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEmpty()
+
+ fakeNetworkEventFlow.value = validMobileEvent(subId = 1, level = 1)
+
+ assertThat(latest).hasSize(1)
+ assertThat(latest!![0].subscriptionId).isEqualTo(1)
+
+ // Second network event comes in with the same subId, does not create a new subscription
+ fakeNetworkEventFlow.value = validMobileEvent(subId = 1, level = 2)
+
+ assertThat(latest).hasSize(1)
+ assertThat(latest!![0].subscriptionId).isEqualTo(1)
+
+ job.cancel()
+ }
+
+ @Test
+ fun `multiple subscriptions`() =
+ testScope.runTest {
+ var latest: List<SubscriptionModel>? = null
+ val job = underTest.subscriptions.onEach { latest = it }.launchIn(this)
+
+ fakeNetworkEventFlow.value = validMobileEvent(subId = 1)
+ fakeNetworkEventFlow.value = validMobileEvent(subId = 2)
+
+ assertThat(latest).hasSize(2)
+
+ job.cancel()
+ }
+
+ @Test
+ fun `mobile disabled event - disables connection - subId specified - single conn`() =
+ testScope.runTest {
+ var latest: List<SubscriptionModel>? = null
+ val job = underTest.subscriptions.onEach { latest = it }.launchIn(this)
+
+ fakeNetworkEventFlow.value = validMobileEvent(subId = 1, level = 1)
+
+ fakeNetworkEventFlow.value = MobileDisabled(subId = 1)
+
+ assertThat(latest).hasSize(0)
+
+ job.cancel()
+ }
+
+ @Test
+ fun `mobile disabled event - disables connection - subId not specified - single conn`() =
+ testScope.runTest {
+ var latest: List<SubscriptionModel>? = null
+ val job = underTest.subscriptions.onEach { latest = it }.launchIn(this)
+
+ fakeNetworkEventFlow.value = validMobileEvent(subId = 1, level = 1)
+
+ fakeNetworkEventFlow.value = MobileDisabled(subId = null)
+
+ assertThat(latest).hasSize(0)
+
+ job.cancel()
+ }
+
+ @Test
+ fun `mobile disabled event - disables connection - subId specified - multiple conn`() =
+ testScope.runTest {
+ var latest: List<SubscriptionModel>? = null
+ val job = underTest.subscriptions.onEach { latest = it }.launchIn(this)
+
+ fakeNetworkEventFlow.value = validMobileEvent(subId = 1, level = 1)
+ fakeNetworkEventFlow.value = validMobileEvent(subId = 2, level = 1)
+
+ fakeNetworkEventFlow.value = MobileDisabled(subId = 2)
+
+ assertThat(latest).hasSize(1)
+
+ job.cancel()
+ }
+
+ @Test
+ fun `mobile disabled event - subId not specified - multiple conn - ignores command`() =
+ testScope.runTest {
+ var latest: List<SubscriptionModel>? = null
+ val job = underTest.subscriptions.onEach { latest = it }.launchIn(this)
+
+ fakeNetworkEventFlow.value = validMobileEvent(subId = 1, level = 1)
+ fakeNetworkEventFlow.value = validMobileEvent(subId = 2, level = 1)
+
+ fakeNetworkEventFlow.value = MobileDisabled(subId = null)
+
+ assertThat(latest).hasSize(2)
+
+ job.cancel()
+ }
+
+ /** Regression test for b/261706421 */
+ @Test
+ fun `multiple connections - remove all - does not throw`() =
+ testScope.runTest {
+ var latest: List<SubscriptionModel>? = null
+ val job = underTest.subscriptions.onEach { latest = it }.launchIn(this)
+
+ // Two subscriptions are added
+ fakeNetworkEventFlow.value = validMobileEvent(subId = 1, level = 1)
+ fakeNetworkEventFlow.value = validMobileEvent(subId = 2, level = 1)
+
+ // Then both are removed by turning off demo mode
+ underTest.stopProcessingCommands()
+
+ assertThat(latest).isEmpty()
+
+ job.cancel()
+ }
+
+ @Test
+ fun `demo connection - single subscription`() =
+ testScope.runTest {
+ var currentEvent: FakeNetworkEventModel = validMobileEvent(subId = 1)
+ var connections: List<DemoMobileConnectionRepository>? = null
+ val job =
+ underTest.subscriptions
+ .onEach { infos ->
+ connections =
+ infos.map { info -> underTest.getRepoForSubId(info.subscriptionId) }
+ }
+ .launchIn(this)
+
+ fakeNetworkEventFlow.value = currentEvent
+
+ assertThat(connections).hasSize(1)
+ val connection1 = connections!![0]
+
+ assertConnection(connection1, currentEvent)
+
+ // Exercise the whole api
+
+ currentEvent = validMobileEvent(subId = 1, level = 2)
+ fakeNetworkEventFlow.value = currentEvent
+ assertConnection(connection1, currentEvent)
+
+ job.cancel()
+ }
+
+ @Test
+ fun `demo connection - two connections - update second - no affect on first`() =
+ testScope.runTest {
+ var currentEvent1 = validMobileEvent(subId = 1)
+ var connection1: DemoMobileConnectionRepository? = null
+ var currentEvent2 = validMobileEvent(subId = 2)
+ var connection2: DemoMobileConnectionRepository? = null
+ var connections: List<DemoMobileConnectionRepository>? = null
+ val job =
+ underTest.subscriptions
+ .onEach { infos ->
+ connections =
+ infos.map { info -> underTest.getRepoForSubId(info.subscriptionId) }
+ }
+ .launchIn(this)
+
+ fakeNetworkEventFlow.value = currentEvent1
+ fakeNetworkEventFlow.value = currentEvent2
+ assertThat(connections).hasSize(2)
+ connections!!.forEach {
+ if (it.subId == 1) {
+ connection1 = it
+ } else if (it.subId == 2) {
+ connection2 = it
+ } else {
+ Assert.fail("Unexpected subscription")
+ }
+ }
+
+ assertConnection(connection1!!, currentEvent1)
+ assertConnection(connection2!!, currentEvent2)
+
+ // WHEN the event changes for connection 2, it updates, and connection 1 stays the same
+ currentEvent2 = validMobileEvent(subId = 2, activity = DATA_ACTIVITY_INOUT)
+ fakeNetworkEventFlow.value = currentEvent2
+ assertConnection(connection1!!, currentEvent1)
+ assertConnection(connection2!!, currentEvent2)
+
+ // and vice versa
+ currentEvent1 = validMobileEvent(subId = 1, inflateStrength = true)
+ fakeNetworkEventFlow.value = currentEvent1
+ assertConnection(connection1!!, currentEvent1)
+ assertConnection(connection2!!, currentEvent2)
+
+ job.cancel()
+ }
+
+ private fun assertConnection(
+ conn: DemoMobileConnectionRepository,
+ model: FakeNetworkEventModel
+ ) {
+ when (model) {
+ is FakeNetworkEventModel.Mobile -> {
+ val connectionInfo: MobileConnectionModel = conn.connectionInfo.value
+ assertThat(conn.subId).isEqualTo(model.subId)
+ assertThat(connectionInfo.cdmaLevel).isEqualTo(model.level)
+ assertThat(connectionInfo.primaryLevel).isEqualTo(model.level)
+ assertThat(connectionInfo.dataActivityDirection).isEqualTo(model.activity)
+ assertThat(connectionInfo.carrierNetworkChangeActive)
+ .isEqualTo(model.carrierNetworkChange)
+
+ // TODO(b/261029387) check these once we start handling them
+ assertThat(connectionInfo.isEmergencyOnly).isFalse()
+ assertThat(connectionInfo.isGsm).isFalse()
+ assertThat(connectionInfo.dataConnectionState)
+ .isEqualTo(DataConnectionState.Connected)
+ }
+ else -> {}
+ }
+ }
+}
+
+/** Convenience to create a valid fake network event with minimal params */
+fun validMobileEvent(
+ level: Int? = 1,
+ dataType: SignalIcon.MobileIconGroup? = THREE_G,
+ subId: Int? = 1,
+ carrierId: Int? = UNKNOWN_CARRIER_ID,
+ inflateStrength: Boolean? = false,
+ activity: Int? = null,
+ carrierNetworkChange: Boolean = false,
+): FakeNetworkEventModel =
+ FakeNetworkEventModel.Mobile(
+ level = level,
+ dataType = dataType,
+ subId = subId,
+ carrierId = carrierId,
+ inflateStrength = inflateStrength,
+ activity = activity,
+ carrierNetworkChange = carrierNetworkChange,
+ )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
index 5ce51bb62c78..1fc9c60cd9ce 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.pipeline.mobile.data.repository
+package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod
import android.os.UserHandle
import android.provider.Settings
@@ -31,14 +31,18 @@ import android.telephony.TelephonyManager.DATA_CONNECTED
import android.telephony.TelephonyManager.DATA_CONNECTING
import android.telephony.TelephonyManager.DATA_DISCONNECTED
import android.telephony.TelephonyManager.DATA_DISCONNECTING
+import android.telephony.TelephonyManager.DATA_UNKNOWN
import android.telephony.TelephonyManager.NETWORK_TYPE_LTE
import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
-import com.android.systemui.statusbar.pipeline.mobile.data.model.DefaultNetworkType
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
-import com.android.systemui.statusbar.pipeline.mobile.data.model.OverrideNetworkType
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.DefaultNetworkType
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.OverrideNetworkType
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.UnknownNetworkType
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
@@ -70,8 +74,9 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
@Mock private lateinit var logger: ConnectivityPipelineLogger
private val scope = CoroutineScope(IMMEDIATE)
+ private val mobileMappings = FakeMobileMappingsProxy()
private val globalSettings = FakeSettings()
- private val connectionsRepo = FakeMobileConnectionsRepository()
+ private val connectionsRepo = FakeMobileConnectionsRepository(mobileMappings)
@Before
fun setUp() {
@@ -87,6 +92,7 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
globalSettings,
connectionsRepo.defaultDataSubId,
connectionsRepo.globalMobileDataSettingChangedEvent,
+ mobileMappings,
IMMEDIATE,
logger,
scope,
@@ -101,10 +107,10 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
@Test
fun testFlowForSubId_default() =
runBlocking(IMMEDIATE) {
- var latest: MobileSubscriptionModel? = null
- val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
+ var latest: MobileConnectionModel? = null
+ val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
- assertThat(latest).isEqualTo(MobileSubscriptionModel())
+ assertThat(latest).isEqualTo(MobileConnectionModel())
job.cancel()
}
@@ -112,8 +118,8 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
@Test
fun testFlowForSubId_emergencyOnly() =
runBlocking(IMMEDIATE) {
- var latest: MobileSubscriptionModel? = null
- val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
+ var latest: MobileConnectionModel? = null
+ val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
val serviceState = ServiceState()
serviceState.isEmergencyOnly = true
@@ -128,8 +134,8 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
@Test
fun testFlowForSubId_emergencyOnly_toggles() =
runBlocking(IMMEDIATE) {
- var latest: MobileSubscriptionModel? = null
- val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
+ var latest: MobileConnectionModel? = null
+ val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
val callback = getTelephonyCallbackForType<ServiceStateListener>()
val serviceState = ServiceState()
@@ -146,8 +152,8 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
@Test
fun testFlowForSubId_signalStrengths_levelsUpdate() =
runBlocking(IMMEDIATE) {
- var latest: MobileSubscriptionModel? = null
- val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
+ var latest: MobileConnectionModel? = null
+ val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
val callback = getTelephonyCallbackForType<TelephonyCallback.SignalStrengthsListener>()
val strength = signalStrength(gsmLevel = 1, cdmaLevel = 2, isGsm = true)
@@ -163,8 +169,8 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
@Test
fun testFlowForSubId_dataConnectionState_connected() =
runBlocking(IMMEDIATE) {
- var latest: MobileSubscriptionModel? = null
- val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
+ var latest: MobileConnectionModel? = null
+ val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
val callback =
getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
@@ -178,8 +184,8 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
@Test
fun testFlowForSubId_dataConnectionState_connecting() =
runBlocking(IMMEDIATE) {
- var latest: MobileSubscriptionModel? = null
- val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
+ var latest: MobileConnectionModel? = null
+ val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
val callback =
getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
@@ -193,8 +199,8 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
@Test
fun testFlowForSubId_dataConnectionState_disconnected() =
runBlocking(IMMEDIATE) {
- var latest: MobileSubscriptionModel? = null
- val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
+ var latest: MobileConnectionModel? = null
+ val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
val callback =
getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
@@ -208,8 +214,8 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
@Test
fun testFlowForSubId_dataConnectionState_disconnecting() =
runBlocking(IMMEDIATE) {
- var latest: MobileSubscriptionModel? = null
- val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
+ var latest: MobileConnectionModel? = null
+ val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
val callback =
getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
@@ -221,10 +227,25 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
}
@Test
+ fun testFlowForSubId_dataConnectionState_unknown() =
+ runBlocking(IMMEDIATE) {
+ var latest: MobileConnectionModel? = null
+ val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+
+ val callback =
+ getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
+ callback.onDataConnectionStateChanged(DATA_UNKNOWN, 200 /* unused */)
+
+ assertThat(latest?.dataConnectionState).isEqualTo(DataConnectionState.Unknown)
+
+ job.cancel()
+ }
+
+ @Test
fun testFlowForSubId_dataActivity() =
runBlocking(IMMEDIATE) {
- var latest: MobileSubscriptionModel? = null
- val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
+ var latest: MobileConnectionModel? = null
+ val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
val callback = getTelephonyCallbackForType<TelephonyCallback.DataActivityListener>()
callback.onDataActivity(3)
@@ -237,8 +258,8 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
@Test
fun testFlowForSubId_carrierNetworkChange() =
runBlocking(IMMEDIATE) {
- var latest: MobileSubscriptionModel? = null
- val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
+ var latest: MobileConnectionModel? = null
+ val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
val callback = getTelephonyCallbackForType<TelephonyCallback.CarrierNetworkListener>()
callback.onCarrierNetworkChange(true)
@@ -251,11 +272,11 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
@Test
fun subscriptionFlow_networkType_default() =
runBlocking(IMMEDIATE) {
- var latest: MobileSubscriptionModel? = null
- val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
+ var latest: MobileConnectionModel? = null
+ val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
val type = NETWORK_TYPE_UNKNOWN
- val expected = DefaultNetworkType(type)
+ val expected = UnknownNetworkType
assertThat(latest?.resolvedNetworkType).isEqualTo(expected)
@@ -265,12 +286,12 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
@Test
fun subscriptionFlow_networkType_updatesUsingDefault() =
runBlocking(IMMEDIATE) {
- var latest: MobileSubscriptionModel? = null
- val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
+ var latest: MobileConnectionModel? = null
+ val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
val callback = getTelephonyCallbackForType<TelephonyCallback.DisplayInfoListener>()
val type = NETWORK_TYPE_LTE
- val expected = DefaultNetworkType(type)
+ val expected = DefaultNetworkType(type, mobileMappings.toIconKey(type))
val ti = mock<TelephonyDisplayInfo>().also { whenever(it.networkType).thenReturn(type) }
callback.onDisplayInfoChanged(ti)
@@ -282,14 +303,15 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
@Test
fun subscriptionFlow_networkType_updatesUsingOverride() =
runBlocking(IMMEDIATE) {
- var latest: MobileSubscriptionModel? = null
- val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
+ var latest: MobileConnectionModel? = null
+ val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
val callback = getTelephonyCallbackForType<TelephonyCallback.DisplayInfoListener>()
val type = OVERRIDE_NETWORK_TYPE_LTE_CA
- val expected = OverrideNetworkType(type)
+ val expected = OverrideNetworkType(type, mobileMappings.toIconKeyOverride(type))
val ti =
mock<TelephonyDisplayInfo>().also {
+ whenever(it.networkType).thenReturn(type)
whenever(it.overrideNetworkType).thenReturn(type)
}
callback.onDisplayInfoChanged(ti)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
index a953a3d802e6..4b82b398360d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.pipeline.mobile.data.repository
+package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod
import android.content.Intent
import android.net.ConnectivityManager
@@ -32,6 +32,8 @@ import androidx.test.filters.SmallTest
import com.android.internal.telephony.PhoneConstants
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
+import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
@@ -50,6 +52,7 @@ import org.junit.After
import org.junit.Assert.assertThrows
import org.junit.Before
import org.junit.Test
+import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@@ -65,6 +68,8 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
@Mock private lateinit var telephonyManager: TelephonyManager
@Mock private lateinit var logger: ConnectivityPipelineLogger
+ private val mobileMappings = FakeMobileMappingsProxy()
+
private val scope = CoroutineScope(IMMEDIATE)
private val globalSettings = FakeSettings()
@@ -72,18 +77,37 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
fun setUp() {
MockitoAnnotations.initMocks(this)
+ // Set up so the individual connection repositories
+ whenever(telephonyManager.createForSubscriptionId(anyInt())).thenAnswer { invocation ->
+ telephonyManager.also {
+ whenever(telephonyManager.subscriptionId).thenReturn(invocation.getArgument(0))
+ }
+ }
+
+ val connectionFactory: MobileConnectionRepositoryImpl.Factory =
+ MobileConnectionRepositoryImpl.Factory(
+ context = context,
+ telephonyManager = telephonyManager,
+ bgDispatcher = IMMEDIATE,
+ globalSettings = globalSettings,
+ logger = logger,
+ mobileMappingsProxy = mobileMappings,
+ scope = scope,
+ )
+
underTest =
MobileConnectionsRepositoryImpl(
connectivityManager,
subscriptionManager,
telephonyManager,
logger,
+ mobileMappings,
fakeBroadcastDispatcher,
globalSettings,
context,
IMMEDIATE,
scope,
- mock(),
+ connectionFactory,
)
}
@@ -95,21 +119,21 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
@Test
fun testSubscriptions_initiallyEmpty() =
runBlocking(IMMEDIATE) {
- assertThat(underTest.subscriptionsFlow.value).isEqualTo(listOf<SubscriptionInfo>())
+ assertThat(underTest.subscriptions.value).isEqualTo(listOf<SubscriptionModel>())
}
@Test
fun testSubscriptions_listUpdates() =
runBlocking(IMMEDIATE) {
- var latest: List<SubscriptionInfo>? = null
+ var latest: List<SubscriptionModel>? = null
- val job = underTest.subscriptionsFlow.onEach { latest = it }.launchIn(this)
+ val job = underTest.subscriptions.onEach { latest = it }.launchIn(this)
whenever(subscriptionManager.completeActiveSubscriptionInfoList)
.thenReturn(listOf(SUB_1, SUB_2))
getSubscriptionCallback().onSubscriptionsChanged()
- assertThat(latest).isEqualTo(listOf(SUB_1, SUB_2))
+ assertThat(latest).isEqualTo(listOf(MODEL_1, MODEL_2))
job.cancel()
}
@@ -117,9 +141,9 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
@Test
fun testSubscriptions_removingSub_updatesList() =
runBlocking(IMMEDIATE) {
- var latest: List<SubscriptionInfo>? = null
+ var latest: List<SubscriptionModel>? = null
- val job = underTest.subscriptionsFlow.onEach { latest = it }.launchIn(this)
+ val job = underTest.subscriptions.onEach { latest = it }.launchIn(this)
// WHEN 2 networks show up
whenever(subscriptionManager.completeActiveSubscriptionInfoList)
@@ -132,7 +156,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
getSubscriptionCallback().onSubscriptionsChanged()
// THEN the subscriptions list represents the newest change
- assertThat(latest).isEqualTo(listOf(SUB_2))
+ assertThat(latest).isEqualTo(listOf(MODEL_2))
job.cancel()
}
@@ -162,7 +186,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
@Test
fun testConnectionRepository_validSubId_isCached() =
runBlocking(IMMEDIATE) {
- val job = underTest.subscriptionsFlow.launchIn(this)
+ val job = underTest.subscriptions.launchIn(this)
whenever(subscriptionManager.completeActiveSubscriptionInfoList)
.thenReturn(listOf(SUB_1))
@@ -179,7 +203,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
@Test
fun testConnectionCache_clearsInvalidSubscriptions() =
runBlocking(IMMEDIATE) {
- val job = underTest.subscriptionsFlow.launchIn(this)
+ val job = underTest.subscriptions.launchIn(this)
whenever(subscriptionManager.completeActiveSubscriptionInfoList)
.thenReturn(listOf(SUB_1, SUB_2))
@@ -202,10 +226,36 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
job.cancel()
}
+ /** Regression test for b/261706421 */
+ @Test
+ fun testConnectionsCache_clearMultipleSubscriptionsAtOnce_doesNotThrow() =
+ runBlocking(IMMEDIATE) {
+ val job = underTest.subscriptions.launchIn(this)
+
+ whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+ .thenReturn(listOf(SUB_1, SUB_2))
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ // Get repos to trigger caching
+ val repo1 = underTest.getRepoForSubId(SUB_1_ID)
+ val repo2 = underTest.getRepoForSubId(SUB_2_ID)
+
+ assertThat(underTest.getSubIdRepoCache())
+ .containsExactly(SUB_1_ID, repo1, SUB_2_ID, repo2)
+
+ // All subscriptions disappear
+ whenever(subscriptionManager.completeActiveSubscriptionInfoList).thenReturn(listOf())
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ assertThat(underTest.getSubIdRepoCache()).isEmpty()
+
+ job.cancel()
+ }
+
@Test
fun testConnectionRepository_invalidSubId_throws() =
runBlocking(IMMEDIATE) {
- val job = underTest.subscriptionsFlow.launchIn(this)
+ val job = underTest.subscriptions.launchIn(this)
assertThrows(IllegalArgumentException::class.java) {
underTest.getRepoForSubId(SUB_1_ID)
@@ -371,10 +421,12 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
private const val SUB_1_ID = 1
private val SUB_1 =
mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_1_ID) }
+ private val MODEL_1 = SubscriptionModel(subscriptionId = SUB_1_ID)
private const val SUB_2_ID = 2
private val SUB_2 =
mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_2_ID) }
+ private val MODEL_2 = SubscriptionModel(subscriptionId = SUB_2_ID)
private const val NET_ID = 123
private val NETWORK = mock<Network>().apply { whenever(getNetId()).thenReturn(NET_ID) }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
index 061c3b54650e..0d4044db71e0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
@@ -16,14 +16,15 @@
package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
-import android.telephony.SubscriptionInfo
import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO
import android.telephony.TelephonyManager.NETWORK_TYPE_GSM
import android.telephony.TelephonyManager.NETWORK_TYPE_LTE
import android.telephony.TelephonyManager.NETWORK_TYPE_UMTS
import com.android.settingslib.SignalIcon.MobileIconGroup
import com.android.settingslib.mobile.TelephonyIcons
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
class FakeMobileIconsInteractor(mobileMappings: MobileMappingsProxy) : MobileIconsInteractor {
@@ -47,8 +48,8 @@ class FakeMobileIconsInteractor(mobileMappings: MobileMappingsProxy) : MobileIco
override val isDefaultConnectionFailed = MutableStateFlow(false)
- private val _filteredSubscriptions = MutableStateFlow<List<SubscriptionInfo>>(listOf())
- override val filteredSubscriptions = _filteredSubscriptions
+ private val _filteredSubscriptions = MutableStateFlow<List<SubscriptionModel>>(listOf())
+ override val filteredSubscriptions: Flow<List<SubscriptionModel>> = _filteredSubscriptions
private val _activeDataConnectionHasDataEnabled = MutableStateFlow(false)
override val activeDataConnectionHasDataEnabled = _activeDataConnectionHasDataEnabled
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
index 7fc1c0f6272c..fd41b5bebd39 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
@@ -24,9 +24,9 @@ import com.android.settingslib.SignalIcon.MobileIconGroup
import com.android.settingslib.mobile.TelephonyIcons
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
-import com.android.systemui.statusbar.pipeline.mobile.data.model.DefaultNetworkType
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
-import com.android.systemui.statusbar.pipeline.mobile.data.model.OverrideNetworkType
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.DefaultNetworkType
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.OverrideNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor.Companion.FIVE_G_OVERRIDE
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor.Companion.FOUR_G
@@ -49,7 +49,7 @@ class MobileIconInteractorTest : SysuiTestCase() {
private lateinit var underTest: MobileIconInteractor
private val mobileMappingsProxy = FakeMobileMappingsProxy()
private val mobileIconsInteractor = FakeMobileIconsInteractor(mobileMappingsProxy)
- private val connectionRepository = FakeMobileConnectionRepository()
+ private val connectionRepository = FakeMobileConnectionRepository(SUB_1_ID)
private val scope = CoroutineScope(IMMEDIATE)
@@ -62,7 +62,6 @@ class MobileIconInteractorTest : SysuiTestCase() {
mobileIconsInteractor.defaultMobileIconMapping,
mobileIconsInteractor.defaultMobileIconGroup,
mobileIconsInteractor.isDefaultConnectionFailed,
- mobileMappingsProxy,
connectionRepository,
)
}
@@ -70,8 +69,8 @@ class MobileIconInteractorTest : SysuiTestCase() {
@Test
fun gsm_level_default_unknown() =
runBlocking(IMMEDIATE) {
- connectionRepository.setMobileSubscriptionModel(
- MobileSubscriptionModel(isGsm = true),
+ connectionRepository.setConnectionInfo(
+ MobileConnectionModel(isGsm = true),
)
var latest: Int? = null
@@ -85,8 +84,8 @@ class MobileIconInteractorTest : SysuiTestCase() {
@Test
fun gsm_usesGsmLevel() =
runBlocking(IMMEDIATE) {
- connectionRepository.setMobileSubscriptionModel(
- MobileSubscriptionModel(
+ connectionRepository.setConnectionInfo(
+ MobileConnectionModel(
isGsm = true,
primaryLevel = GSM_LEVEL,
cdmaLevel = CDMA_LEVEL
@@ -104,8 +103,8 @@ class MobileIconInteractorTest : SysuiTestCase() {
@Test
fun cdma_level_default_unknown() =
runBlocking(IMMEDIATE) {
- connectionRepository.setMobileSubscriptionModel(
- MobileSubscriptionModel(isGsm = false),
+ connectionRepository.setConnectionInfo(
+ MobileConnectionModel(isGsm = false),
)
var latest: Int? = null
@@ -118,8 +117,8 @@ class MobileIconInteractorTest : SysuiTestCase() {
@Test
fun cdma_usesCdmaLevel() =
runBlocking(IMMEDIATE) {
- connectionRepository.setMobileSubscriptionModel(
- MobileSubscriptionModel(
+ connectionRepository.setConnectionInfo(
+ MobileConnectionModel(
isGsm = false,
primaryLevel = GSM_LEVEL,
cdmaLevel = CDMA_LEVEL
@@ -137,8 +136,11 @@ class MobileIconInteractorTest : SysuiTestCase() {
@Test
fun iconGroup_three_g() =
runBlocking(IMMEDIATE) {
- connectionRepository.setMobileSubscriptionModel(
- MobileSubscriptionModel(resolvedNetworkType = DefaultNetworkType(THREE_G)),
+ connectionRepository.setConnectionInfo(
+ MobileConnectionModel(
+ resolvedNetworkType =
+ DefaultNetworkType(THREE_G, mobileMappingsProxy.toIconKey(THREE_G))
+ ),
)
var latest: MobileIconGroup? = null
@@ -152,16 +154,23 @@ class MobileIconInteractorTest : SysuiTestCase() {
@Test
fun iconGroup_updates_on_change() =
runBlocking(IMMEDIATE) {
- connectionRepository.setMobileSubscriptionModel(
- MobileSubscriptionModel(resolvedNetworkType = DefaultNetworkType(THREE_G)),
+ connectionRepository.setConnectionInfo(
+ MobileConnectionModel(
+ resolvedNetworkType =
+ DefaultNetworkType(THREE_G, mobileMappingsProxy.toIconKey(THREE_G))
+ ),
)
var latest: MobileIconGroup? = null
val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this)
- connectionRepository.setMobileSubscriptionModel(
- MobileSubscriptionModel(
- resolvedNetworkType = DefaultNetworkType(FOUR_G),
+ connectionRepository.setConnectionInfo(
+ MobileConnectionModel(
+ resolvedNetworkType =
+ DefaultNetworkType(
+ FOUR_G,
+ mobileMappingsProxy.toIconKey(FOUR_G),
+ ),
),
)
yield()
@@ -174,8 +183,14 @@ class MobileIconInteractorTest : SysuiTestCase() {
@Test
fun iconGroup_5g_override_type() =
runBlocking(IMMEDIATE) {
- connectionRepository.setMobileSubscriptionModel(
- MobileSubscriptionModel(resolvedNetworkType = OverrideNetworkType(FIVE_G_OVERRIDE)),
+ connectionRepository.setConnectionInfo(
+ MobileConnectionModel(
+ resolvedNetworkType =
+ OverrideNetworkType(
+ FIVE_G_OVERRIDE,
+ mobileMappingsProxy.toIconKeyOverride(FIVE_G_OVERRIDE)
+ )
+ ),
)
var latest: MobileIconGroup? = null
@@ -189,9 +204,13 @@ class MobileIconInteractorTest : SysuiTestCase() {
@Test
fun iconGroup_default_if_no_lookup() =
runBlocking(IMMEDIATE) {
- connectionRepository.setMobileSubscriptionModel(
- MobileSubscriptionModel(
- resolvedNetworkType = DefaultNetworkType(NETWORK_TYPE_UNKNOWN),
+ connectionRepository.setConnectionInfo(
+ MobileConnectionModel(
+ resolvedNetworkType =
+ DefaultNetworkType(
+ NETWORK_TYPE_UNKNOWN,
+ mobileMappingsProxy.toIconKey(NETWORK_TYPE_UNKNOWN)
+ ),
),
)
@@ -238,8 +257,8 @@ class MobileIconInteractorTest : SysuiTestCase() {
var latest: Boolean? = null
val job = underTest.isDataConnected.onEach { latest = it }.launchIn(this)
- connectionRepository.setMobileSubscriptionModel(
- MobileSubscriptionModel(dataConnectionState = DataConnectionState.Connected)
+ connectionRepository.setConnectionInfo(
+ MobileConnectionModel(dataConnectionState = DataConnectionState.Connected)
)
yield()
@@ -254,8 +273,8 @@ class MobileIconInteractorTest : SysuiTestCase() {
var latest: Boolean? = null
val job = underTest.isDataConnected.onEach { latest = it }.launchIn(this)
- connectionRepository.setMobileSubscriptionModel(
- MobileSubscriptionModel(dataConnectionState = DataConnectionState.Disconnected)
+ connectionRepository.setConnectionInfo(
+ MobileConnectionModel(dataConnectionState = DataConnectionState.Disconnected)
)
assertThat(latest).isFalse()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
index b56dcd752557..58e57e298e51 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
@@ -16,17 +16,16 @@
package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
-import android.telephony.SubscriptionInfo
import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.util.CarrierConfigTracker
-import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
@@ -45,8 +44,8 @@ import org.mockito.MockitoAnnotations
class MobileIconsInteractorTest : SysuiTestCase() {
private lateinit var underTest: MobileIconsInteractor
private val userSetupRepository = FakeUserSetupRepository()
- private val connectionsRepository = FakeMobileConnectionsRepository()
private val mobileMappingsProxy = FakeMobileMappingsProxy()
+ private val connectionsRepository = FakeMobileConnectionsRepository(mobileMappingsProxy)
private val scope = CoroutineScope(IMMEDIATE)
@Mock private lateinit var carrierConfigTracker: CarrierConfigTracker
@@ -69,7 +68,6 @@ class MobileIconsInteractorTest : SysuiTestCase() {
MobileIconsInteractorImpl(
connectionsRepository,
carrierConfigTracker,
- mobileMappingsProxy,
userSetupRepository,
scope
)
@@ -80,10 +78,10 @@ class MobileIconsInteractorTest : SysuiTestCase() {
@Test
fun filteredSubscriptions_default() =
runBlocking(IMMEDIATE) {
- var latest: List<SubscriptionInfo>? = null
+ var latest: List<SubscriptionModel>? = null
val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
- assertThat(latest).isEqualTo(listOf<SubscriptionInfo>())
+ assertThat(latest).isEqualTo(listOf<SubscriptionModel>())
job.cancel()
}
@@ -93,7 +91,7 @@ class MobileIconsInteractorTest : SysuiTestCase() {
runBlocking(IMMEDIATE) {
connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_2))
- var latest: List<SubscriptionInfo>? = null
+ var latest: List<SubscriptionModel>? = null
val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
assertThat(latest).isEqualTo(listOf(SUB_1, SUB_2))
@@ -109,7 +107,7 @@ class MobileIconsInteractorTest : SysuiTestCase() {
whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
.thenReturn(false)
- var latest: List<SubscriptionInfo>? = null
+ var latest: List<SubscriptionModel>? = null
val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
// Filtered subscriptions should show the active one when the config is false
@@ -126,7 +124,7 @@ class MobileIconsInteractorTest : SysuiTestCase() {
whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
.thenReturn(false)
- var latest: List<SubscriptionInfo>? = null
+ var latest: List<SubscriptionModel>? = null
val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
// Filtered subscriptions should show the active one when the config is false
@@ -143,7 +141,7 @@ class MobileIconsInteractorTest : SysuiTestCase() {
whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
.thenReturn(true)
- var latest: List<SubscriptionInfo>? = null
+ var latest: List<SubscriptionModel>? = null
val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
// Filtered subscriptions should show the primary (non-opportunistic) if the config is
@@ -161,7 +159,7 @@ class MobileIconsInteractorTest : SysuiTestCase() {
whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
.thenReturn(true)
- var latest: List<SubscriptionInfo>? = null
+ var latest: List<SubscriptionModel>? = null
val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
// Filtered subscriptions should show the primary (non-opportunistic) if the config is
@@ -261,29 +259,19 @@ class MobileIconsInteractorTest : SysuiTestCase() {
private val IMMEDIATE = Dispatchers.Main.immediate
private const val SUB_1_ID = 1
- private val SUB_1 =
- mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_1_ID) }
- private val CONNECTION_1 = FakeMobileConnectionRepository()
+ private val SUB_1 = SubscriptionModel(subscriptionId = SUB_1_ID)
+ private val CONNECTION_1 = FakeMobileConnectionRepository(SUB_1_ID)
private const val SUB_2_ID = 2
- private val SUB_2 =
- mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_2_ID) }
- private val CONNECTION_2 = FakeMobileConnectionRepository()
+ private val SUB_2 = SubscriptionModel(subscriptionId = SUB_2_ID)
+ private val CONNECTION_2 = FakeMobileConnectionRepository(SUB_2_ID)
private const val SUB_3_ID = 3
- private val SUB_3_OPP =
- mock<SubscriptionInfo>().also {
- whenever(it.subscriptionId).thenReturn(SUB_3_ID)
- whenever(it.isOpportunistic).thenReturn(true)
- }
- private val CONNECTION_3 = FakeMobileConnectionRepository()
+ private val SUB_3_OPP = SubscriptionModel(subscriptionId = SUB_3_ID, isOpportunistic = true)
+ private val CONNECTION_3 = FakeMobileConnectionRepository(SUB_3_ID)
private const val SUB_4_ID = 4
- private val SUB_4_OPP =
- mock<SubscriptionInfo>().also {
- whenever(it.subscriptionId).thenReturn(SUB_4_ID)
- whenever(it.isOpportunistic).thenReturn(true)
- }
- private val CONNECTION_4 = FakeMobileConnectionRepository()
+ private val SUB_4_OPP = SubscriptionModel(subscriptionId = SUB_4_ID, isOpportunistic = true)
+ private val CONNECTION_4 = FakeMobileConnectionRepository(SUB_4_ID)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
index 5c16e1295b65..3d9fd961222f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
@@ -25,6 +25,7 @@ import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.lifecycle.InstantTaskExecutorRule
+import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON
@@ -64,6 +65,7 @@ class ModernStatusBarWifiViewTest : SysuiTestCase() {
private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags
@Mock
private lateinit var logger: ConnectivityPipelineLogger
+ @Mock private lateinit var tableLogBuffer: TableLogBuffer
@Mock
private lateinit var connectivityConstants: ConnectivityConstants
@Mock
@@ -103,6 +105,7 @@ class ModernStatusBarWifiViewTest : SysuiTestCase() {
connectivityConstants,
context,
logger,
+ tableLogBuffer,
interactor,
scope,
statusBarPipelineFlags,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt
index 3001b8162185..12b93819fc5e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt
@@ -23,6 +23,7 @@ import com.android.settingslib.AccessibilityContentDescriptions.WIFI_CONNECTION_
import com.android.settingslib.AccessibilityContentDescriptions.WIFI_NO_CONNECTION
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
+import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_FULL_ICONS
import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_INTERNET_ICONS
import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_NETWORK
@@ -40,6 +41,7 @@ import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepo
import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor
import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractorImpl
import com.android.systemui.statusbar.pipeline.wifi.shared.WifiConstants
+import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel.Companion.NO_INTERNET
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
@@ -67,6 +69,7 @@ internal class WifiViewModelIconParameterizedTest(private val testCase: TestCase
@Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags
@Mock private lateinit var logger: ConnectivityPipelineLogger
+ @Mock private lateinit var tableLogBuffer: TableLogBuffer
@Mock private lateinit var connectivityConstants: ConnectivityConstants
@Mock private lateinit var wifiConstants: WifiConstants
private lateinit var airplaneModeRepository: FakeAirplaneModeRepository
@@ -123,6 +126,7 @@ internal class WifiViewModelIconParameterizedTest(private val testCase: TestCase
connectivityConstants,
context,
logger,
+ tableLogBuffer,
interactor,
scope,
statusBarPipelineFlags,
@@ -137,15 +141,21 @@ internal class WifiViewModelIconParameterizedTest(private val testCase: TestCase
yield()
// THEN we get the expected icon
- assertThat(iconFlow.value?.res).isEqualTo(testCase.expected?.iconResource)
- val expectedContentDescription =
- if (testCase.expected == null) {
- null
- } else {
- testCase.expected.contentDescription.invoke(context)
+ val actualIcon = iconFlow.value
+ when (testCase.expected) {
+ null -> {
+ assertThat(actualIcon).isInstanceOf(WifiIcon.Hidden::class.java)
+ }
+ else -> {
+ assertThat(actualIcon).isInstanceOf(WifiIcon.Visible::class.java)
+ val actualIconVisible = actualIcon as WifiIcon.Visible
+ assertThat(actualIconVisible.icon.res).isEqualTo(testCase.expected.iconResource)
+ val expectedContentDescription =
+ testCase.expected.contentDescription.invoke(context)
+ assertThat(actualIconVisible.contentDescription.loadContentDescription(context))
+ .isEqualTo(expectedContentDescription)
}
- assertThat(iconFlow.value?.contentDescription?.loadContentDescription(context))
- .isEqualTo(expectedContentDescription)
+ }
job.cancel()
}
@@ -174,7 +184,7 @@ internal class WifiViewModelIconParameterizedTest(private val testCase: TestCase
val isDefault: Boolean = false,
val network: WifiNetworkModel,
- /** The expected output. Null if we expect the output to be null. */
+ /** The expected output. Null if we expect the output to be hidden. */
val expected: Expected?
) {
override fun toString(): String {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
index 6a6b2a801ab0..7502020286f0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
@@ -18,7 +18,7 @@ package com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
@@ -34,6 +34,7 @@ import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiIntera
import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractorImpl
import com.android.systemui.statusbar.pipeline.wifi.shared.WifiConstants
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiActivityModel
+import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -59,6 +60,7 @@ class WifiViewModelTest : SysuiTestCase() {
@Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags
@Mock private lateinit var logger: ConnectivityPipelineLogger
+ @Mock private lateinit var tableLogBuffer: TableLogBuffer
@Mock private lateinit var connectivityConstants: ConnectivityConstants
@Mock private lateinit var wifiConstants: WifiConstants
private lateinit var airplaneModeRepository: FakeAirplaneModeRepository
@@ -103,21 +105,21 @@ class WifiViewModelTest : SysuiTestCase() {
@Test
fun wifiIcon_allLocationViewModelsReceiveSameData() = runBlocking(IMMEDIATE) {
- var latestHome: Icon? = null
+ var latestHome: WifiIcon? = null
val jobHome = underTest
.home
.wifiIcon
.onEach { latestHome = it }
.launchIn(this)
- var latestKeyguard: Icon? = null
+ var latestKeyguard: WifiIcon? = null
val jobKeyguard = underTest
.keyguard
.wifiIcon
.onEach { latestKeyguard = it }
.launchIn(this)
- var latestQs: Icon? = null
+ var latestQs: WifiIcon? = null
val jobQs = underTest
.qs
.wifiIcon
@@ -133,7 +135,7 @@ class WifiViewModelTest : SysuiTestCase() {
)
yield()
- assertThat(latestHome).isInstanceOf(Icon.Resource::class.java)
+ assertThat(latestHome).isInstanceOf(WifiIcon.Visible::class.java)
assertThat(latestHome).isEqualTo(latestKeyguard)
assertThat(latestKeyguard).isEqualTo(latestQs)
@@ -541,6 +543,7 @@ class WifiViewModelTest : SysuiTestCase() {
connectivityConstants,
context,
logger,
+ tableLogBuffer,
interactor,
scope,
statusBarPipelineFlags,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
index 47c84ab48093..7014f93fba4a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
@@ -35,6 +35,7 @@ import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.shared.model.Text
+import com.android.systemui.common.shared.model.TintedIcon
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.policy.ConfigurationController
@@ -109,6 +110,35 @@ class ChipbarCoordinatorTest : SysuiTestCase() {
}
@Test
+ fun displayView_contentDescription_iconHasDescription() {
+ underTest.displayView(
+ createChipbarInfo(
+ Icon.Resource(R.drawable.ic_cake, ContentDescription.Loaded("loadedCD")),
+ Text.Loaded("text"),
+ endItem = null,
+ )
+ )
+
+ val contentDescView = getChipbarView().requireViewById<ViewGroup>(R.id.chipbar_inner)
+ assertThat(contentDescView.contentDescription.toString()).contains("loadedCD")
+ assertThat(contentDescView.contentDescription.toString()).contains("text")
+ }
+
+ @Test
+ fun displayView_contentDescription_iconHasNoDescription() {
+ underTest.displayView(
+ createChipbarInfo(
+ Icon.Resource(R.drawable.ic_cake, contentDescription = null),
+ Text.Loaded("text"),
+ endItem = null,
+ )
+ )
+
+ val contentDescView = getChipbarView().requireViewById<ViewGroup>(R.id.chipbar_inner)
+ assertThat(contentDescView.contentDescription.toString()).isEqualTo("text")
+ }
+
+ @Test
fun displayView_loadedIcon_correctlyRendered() {
val drawable = context.getDrawable(R.drawable.ic_celebration)!!
@@ -370,7 +400,7 @@ class ChipbarCoordinatorTest : SysuiTestCase() {
vibrationEffect: VibrationEffect? = null,
): ChipbarInfo {
return ChipbarInfo(
- startIcon,
+ TintedIcon(startIcon, tintAttr = null),
text,
endItem,
vibrationEffect,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/TestableAlertDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/TestableAlertDialogTest.kt
new file mode 100644
index 000000000000..01dd60ae2200
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/TestableAlertDialogTest.kt
@@ -0,0 +1,333 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.util
+
+import android.content.DialogInterface
+import android.content.DialogInterface.BUTTON_NEGATIVE
+import android.content.DialogInterface.BUTTON_NEUTRAL
+import android.content.DialogInterface.BUTTON_POSITIVE
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.inOrder
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class TestableAlertDialogTest : SysuiTestCase() {
+
+ @Test
+ fun dialogNotShowingWhenCreated() {
+ val dialog = TestableAlertDialog(context)
+
+ assertThat(dialog.isShowing).isFalse()
+ }
+
+ @Test
+ fun dialogShownDoesntCrash() {
+ val dialog = TestableAlertDialog(context)
+
+ dialog.show()
+ }
+
+ @Test
+ fun dialogShowing() {
+ val dialog = TestableAlertDialog(context)
+
+ dialog.show()
+
+ assertThat(dialog.isShowing).isTrue()
+ }
+
+ @Test
+ fun showListenerCalled() {
+ val dialog = TestableAlertDialog(context)
+ val listener: DialogInterface.OnShowListener = mock()
+ dialog.setOnShowListener(listener)
+
+ dialog.show()
+
+ verify(listener).onShow(dialog)
+ }
+
+ @Test
+ fun showListenerRemoved() {
+ val dialog = TestableAlertDialog(context)
+ val listener: DialogInterface.OnShowListener = mock()
+ dialog.setOnShowListener(listener)
+ dialog.setOnShowListener(null)
+
+ dialog.show()
+
+ verify(listener, never()).onShow(any())
+ }
+
+ @Test
+ fun dialogHiddenNotShowing() {
+ val dialog = TestableAlertDialog(context)
+
+ dialog.show()
+ dialog.hide()
+
+ assertThat(dialog.isShowing).isFalse()
+ }
+
+ @Test
+ fun dialogDismissNotShowing() {
+ val dialog = TestableAlertDialog(context)
+
+ dialog.show()
+ dialog.dismiss()
+
+ assertThat(dialog.isShowing).isFalse()
+ }
+
+ @Test
+ fun dismissListenerCalled_ifShowing() {
+ val dialog = TestableAlertDialog(context)
+ val listener: DialogInterface.OnDismissListener = mock()
+ dialog.setOnDismissListener(listener)
+
+ dialog.show()
+ dialog.dismiss()
+
+ verify(listener).onDismiss(dialog)
+ }
+
+ @Test
+ fun dismissListenerNotCalled_ifNotShowing() {
+ val dialog = TestableAlertDialog(context)
+ val listener: DialogInterface.OnDismissListener = mock()
+ dialog.setOnDismissListener(listener)
+
+ dialog.dismiss()
+
+ verify(listener, never()).onDismiss(any())
+ }
+
+ @Test
+ fun dismissListenerRemoved() {
+ val dialog = TestableAlertDialog(context)
+ val listener: DialogInterface.OnDismissListener = mock()
+ dialog.setOnDismissListener(listener)
+ dialog.setOnDismissListener(null)
+
+ dialog.show()
+ dialog.dismiss()
+
+ verify(listener, never()).onDismiss(any())
+ }
+
+ @Test
+ fun cancelListenerCalled_showing() {
+ val dialog = TestableAlertDialog(context)
+ val listener: DialogInterface.OnCancelListener = mock()
+ dialog.setOnCancelListener(listener)
+
+ dialog.show()
+ dialog.cancel()
+
+ verify(listener).onCancel(dialog)
+ }
+
+ @Test
+ fun cancelListenerCalled_notShowing() {
+ val dialog = TestableAlertDialog(context)
+ val listener: DialogInterface.OnCancelListener = mock()
+ dialog.setOnCancelListener(listener)
+
+ dialog.cancel()
+
+ verify(listener).onCancel(dialog)
+ }
+
+ @Test
+ fun dismissCalledOnCancel_showing() {
+ val dialog = TestableAlertDialog(context)
+ val listener: DialogInterface.OnDismissListener = mock()
+ dialog.setOnDismissListener(listener)
+
+ dialog.show()
+ dialog.cancel()
+
+ verify(listener).onDismiss(dialog)
+ }
+
+ @Test
+ fun dialogCancelNotShowing() {
+ val dialog = TestableAlertDialog(context)
+
+ dialog.show()
+ dialog.cancel()
+
+ assertThat(dialog.isShowing).isFalse()
+ }
+
+ @Test
+ fun cancelListenerRemoved() {
+ val dialog = TestableAlertDialog(context)
+ val listener: DialogInterface.OnCancelListener = mock()
+ dialog.setOnCancelListener(listener)
+ dialog.setOnCancelListener(null)
+
+ dialog.show()
+ dialog.cancel()
+
+ verify(listener, never()).onCancel(any())
+ }
+
+ @Test
+ fun positiveButtonClick() {
+ val dialog = TestableAlertDialog(context)
+ val listener: DialogInterface.OnClickListener = mock()
+ dialog.setButton(BUTTON_POSITIVE, "", listener)
+
+ dialog.show()
+ dialog.clickButton(BUTTON_POSITIVE)
+
+ verify(listener).onClick(dialog, BUTTON_POSITIVE)
+ }
+
+ @Test
+ fun positiveButtonListener_noCalledWhenClickOtherButtons() {
+ val dialog = TestableAlertDialog(context)
+ val listener: DialogInterface.OnClickListener = mock()
+ dialog.setButton(BUTTON_POSITIVE, "", listener)
+
+ dialog.show()
+ dialog.clickButton(BUTTON_NEUTRAL)
+ dialog.clickButton(BUTTON_NEGATIVE)
+
+ verify(listener, never()).onClick(any(), anyInt())
+ }
+
+ @Test
+ fun negativeButtonClick() {
+ val dialog = TestableAlertDialog(context)
+ val listener: DialogInterface.OnClickListener = mock()
+ dialog.setButton(BUTTON_NEGATIVE, "", listener)
+
+ dialog.show()
+ dialog.clickButton(BUTTON_NEGATIVE)
+
+ verify(listener).onClick(dialog, DialogInterface.BUTTON_NEGATIVE)
+ }
+
+ @Test
+ fun negativeButtonListener_noCalledWhenClickOtherButtons() {
+ val dialog = TestableAlertDialog(context)
+ val listener: DialogInterface.OnClickListener = mock()
+ dialog.setButton(BUTTON_NEGATIVE, "", listener)
+
+ dialog.show()
+ dialog.clickButton(BUTTON_NEUTRAL)
+ dialog.clickButton(BUTTON_POSITIVE)
+
+ verify(listener, never()).onClick(any(), anyInt())
+ }
+
+ @Test
+ fun neutralButtonClick() {
+ val dialog = TestableAlertDialog(context)
+ val listener: DialogInterface.OnClickListener = mock()
+ dialog.setButton(BUTTON_NEUTRAL, "", listener)
+
+ dialog.show()
+ dialog.clickButton(BUTTON_NEUTRAL)
+
+ verify(listener).onClick(dialog, BUTTON_NEUTRAL)
+ }
+
+ @Test
+ fun neutralButtonListener_noCalledWhenClickOtherButtons() {
+ val dialog = TestableAlertDialog(context)
+ val listener: DialogInterface.OnClickListener = mock()
+ dialog.setButton(BUTTON_NEUTRAL, "", listener)
+
+ dialog.show()
+ dialog.clickButton(BUTTON_POSITIVE)
+ dialog.clickButton(BUTTON_NEGATIVE)
+
+ verify(listener, never()).onClick(any(), anyInt())
+ }
+
+ @Test
+ fun sameClickListenerCalledCorrectly() {
+ val dialog = TestableAlertDialog(context)
+ val listener: DialogInterface.OnClickListener = mock()
+ dialog.setButton(BUTTON_POSITIVE, "", listener)
+ dialog.setButton(BUTTON_NEUTRAL, "", listener)
+ dialog.setButton(BUTTON_NEGATIVE, "", listener)
+
+ dialog.show()
+ dialog.clickButton(BUTTON_POSITIVE)
+ dialog.clickButton(BUTTON_NEGATIVE)
+ dialog.clickButton(BUTTON_NEUTRAL)
+
+ val inOrder = inOrder(listener)
+ inOrder.verify(listener).onClick(dialog, BUTTON_POSITIVE)
+ inOrder.verify(listener).onClick(dialog, BUTTON_NEGATIVE)
+ inOrder.verify(listener).onClick(dialog, BUTTON_NEUTRAL)
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun clickBadButton() {
+ val dialog = TestableAlertDialog(context)
+
+ dialog.clickButton(10000)
+ }
+
+ @Test
+ fun clickButtonDismisses_positive() {
+ val dialog = TestableAlertDialog(context)
+
+ dialog.show()
+ dialog.clickButton(BUTTON_POSITIVE)
+
+ assertThat(dialog.isShowing).isFalse()
+ }
+
+ @Test
+ fun clickButtonDismisses_negative() {
+ val dialog = TestableAlertDialog(context)
+
+ dialog.show()
+ dialog.clickButton(BUTTON_NEGATIVE)
+
+ assertThat(dialog.isShowing).isFalse()
+ }
+
+ @Test
+ fun clickButtonDismisses_neutral() {
+ val dialog = TestableAlertDialog(context)
+
+ dialog.show()
+ dialog.clickButton(BUTTON_NEUTRAL)
+
+ assertThat(dialog.isShowing).isFalse()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
index 3769f52456fb..915ea1a8cd5a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
@@ -170,6 +170,34 @@ public class VolumeDialogControllerImplTest extends SysuiTestCase {
}
@Test
+ public void testVolumeChangeW_deviceOutFromBLEHeadset_doStateChanged() {
+ mVolumeController.setDeviceInteractive(false);
+ when(mWakefullnessLifcycle.getWakefulness()).thenReturn(
+ WakefulnessLifecycle.WAKEFULNESS_AWAKE);
+ when(mAudioManager.getDevicesForStream(AudioManager.STREAM_VOICE_CALL)).thenReturn(
+ AudioManager.DEVICE_OUT_BLE_HEADSET);
+
+ mVolumeController.onVolumeChangedW(
+ AudioManager.STREAM_VOICE_CALL, AudioManager.FLAG_SHOW_UI);
+
+ verify(mCallback, times(1)).onStateChanged(any());
+ }
+
+ @Test
+ public void testVolumeChangeW_deviceOutFromA2DP_doStateChanged() {
+ mVolumeController.setDeviceInteractive(false);
+ when(mWakefullnessLifcycle.getWakefulness()).thenReturn(
+ WakefulnessLifecycle.WAKEFULNESS_AWAKE);
+ when(mAudioManager.getDevicesForStream(AudioManager.STREAM_VOICE_CALL)).thenReturn(
+ AudioManager.DEVICE_OUT_BLUETOOTH_A2DP);
+
+ mVolumeController.onVolumeChangedW(
+ AudioManager.STREAM_VOICE_CALL, AudioManager.FLAG_SHOW_UI);
+
+ verify(mCallback, never()).onStateChanged(any());
+ }
+
+ @Test
public void testOnRemoteVolumeChanged_newStream_noNullPointer() {
MediaSession.Token token = new MediaSession.Token(Process.myUid(), null);
mVolumeController.mMediaSessionsCallbacksW.onRemoteVolumeChanged(token, 0);
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/MemoryTrackingTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/MemoryTrackingTestCase.java
new file mode 100644
index 000000000000..3767fbe98dc1
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/MemoryTrackingTestCase.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui;
+
+import android.os.Debug;
+import android.util.Log;
+
+import org.junit.AfterClass;
+import org.junit.Before;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Convenience class for grabbing a heap dump after a test class is run.
+ *
+ * To use:
+ * - locally edit your test class to inherit from MemoryTrackingTestCase instead of SysuiTestCase
+ * - Watch the logcat with tag MEMORY to see the path to the .ahprof file
+ * - adb pull /path/to/something.ahprof
+ * - Download ahat from https://sites.google.com/corp/google.com/ahat/home
+ * - java -jar ~/Downloads/ahat-1.7.2.jar something.hprof
+ * - Watch output for next steps
+ * - Profit and fix leaks!
+ */
+public class MemoryTrackingTestCase extends SysuiTestCase {
+ private static File sFilesDir = null;
+ private static String sLatestTestClassName = null;
+
+ @Before public void grabFilesDir() {
+ if (sFilesDir == null) {
+ sFilesDir = mContext.getFilesDir();
+ }
+ sLatestTestClassName = getClass().getName();
+ }
+
+ @AfterClass
+ public static void dumpHeap() throws IOException {
+ if (sFilesDir == null) {
+ Log.e("MEMORY", "Somehow no test cases??");
+ return;
+ }
+ mockitoTearDown();
+ Log.w("MEMORY", "about to dump heap");
+ File path = new File(sFilesDir, sLatestTestClassName + ".ahprof");
+ Debug.dumpHprofData(path.getPath());
+ Log.w("MEMORY", "did it! Location: " + path);
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt
index 52e0c982cae0..ad086ff9c664 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt
@@ -22,12 +22,12 @@ import android.content.IntentFilter
import android.os.Handler
import android.os.Looper
import android.os.UserHandle
-import android.util.ArraySet
import android.util.Log
import com.android.systemui.SysuiTestableContext
import com.android.systemui.broadcast.logging.BroadcastDispatcherLogger
import com.android.systemui.dump.DumpManager
import com.android.systemui.settings.UserTracker
+import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.Executor
class FakeBroadcastDispatcher(
@@ -50,7 +50,7 @@ class FakeBroadcastDispatcher(
PendingRemovalStore(logger)
) {
- val registeredReceivers = ArraySet<BroadcastReceiver>()
+ val registeredReceivers: MutableSet<BroadcastReceiver> = ConcurrentHashMap.newKeySet()
override fun registerReceiverWithHandler(
receiver: BroadcastReceiver,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index 36016678fb91..5c2a915e81b6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -17,11 +17,15 @@
package com.android.systemui.keyguard.data.repository
+import android.graphics.Point
import com.android.systemui.common.shared.model.Position
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
import com.android.systemui.keyguard.shared.model.DozeTransitionModel
import com.android.systemui.keyguard.shared.model.StatusBarState
+import com.android.systemui.keyguard.shared.model.WakeSleepReason
import com.android.systemui.keyguard.shared.model.WakefulnessModel
+import com.android.systemui.keyguard.shared.model.WakefulnessState
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -49,7 +53,7 @@ class FakeKeyguardRepository : KeyguardRepository {
override val isDreaming: Flow<Boolean> = _isDreaming
private val _dozeAmount = MutableStateFlow(0f)
- override val dozeAmount: Flow<Float> = _dozeAmount
+ override val linearDozeAmount: Flow<Float> = _dozeAmount
private val _statusBarState = MutableStateFlow(StatusBarState.SHADE)
override val statusBarState: Flow<StatusBarState> = _statusBarState
@@ -57,8 +61,16 @@ class FakeKeyguardRepository : KeyguardRepository {
private val _dozeTransitionModel = MutableStateFlow(DozeTransitionModel())
override val dozeTransitionModel: Flow<DozeTransitionModel> = _dozeTransitionModel
- private val _wakefulnessState = MutableStateFlow(WakefulnessModel.ASLEEP)
- override val wakefulnessState: Flow<WakefulnessModel> = _wakefulnessState
+ private val _wakefulnessModel =
+ MutableStateFlow(
+ WakefulnessModel(
+ WakefulnessState.ASLEEP,
+ false,
+ WakeSleepReason.OTHER,
+ WakeSleepReason.OTHER
+ )
+ )
+ override val wakefulness: Flow<WakefulnessModel> = _wakefulnessModel
private val _isUdfpsSupported = MutableStateFlow(false)
@@ -71,6 +83,15 @@ class FakeKeyguardRepository : KeyguardRepository {
private val _biometricUnlockState = MutableStateFlow(BiometricUnlockModel.NONE)
override val biometricUnlockState: Flow<BiometricUnlockModel> = _biometricUnlockState
+ private val _fingerprintSensorLocation = MutableStateFlow<Point?>(null)
+ override val fingerprintSensorLocation: Flow<Point?> = _fingerprintSensorLocation
+
+ private val _faceSensorLocation = MutableStateFlow<Point?>(null)
+ override val faceSensorLocation: Flow<Point?> = _faceSensorLocation
+
+ private val _biometricUnlockSource = MutableStateFlow<BiometricUnlockSource?>(null)
+ override val biometricUnlockSource: Flow<BiometricUnlockSource?> = _biometricUnlockSource
+
override fun isKeyguardShowing(): Boolean {
return _isKeyguardShowing.value
}
@@ -99,6 +120,22 @@ class FakeKeyguardRepository : KeyguardRepository {
_dozeAmount.value = dozeAmount
}
+ fun setBiometricUnlockState(state: BiometricUnlockModel) {
+ _biometricUnlockState.tryEmit(state)
+ }
+
+ fun setBiometricUnlockSource(source: BiometricUnlockSource?) {
+ _biometricUnlockSource.tryEmit(source)
+ }
+
+ fun setFaceSensorLocation(location: Point?) {
+ _faceSensorLocation.tryEmit(location)
+ }
+
+ fun setFingerprintSensorLocation(location: Point?) {
+ _fingerprintSensorLocation.tryEmit(location)
+ }
+
override fun isUdfpsSupported(): Boolean {
return _isUdfpsSupported.value
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeLightRevealScrimRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeLightRevealScrimRepository.kt
new file mode 100644
index 000000000000..7c22604dc546
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeLightRevealScrimRepository.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import com.android.systemui.statusbar.LightRevealEffect
+import kotlinx.coroutines.flow.MutableStateFlow
+
+/** Fake implementation of [LightRevealScrimRepository] */
+class FakeLightRevealScrimRepository : LightRevealScrimRepository {
+
+ private val _revealEffect: MutableStateFlow<LightRevealEffect> =
+ MutableStateFlow(DEFAULT_REVEAL_EFFECT)
+ override val revealEffect = _revealEffect
+
+ fun setRevealEffect(effect: LightRevealEffect) {
+ _revealEffect.tryEmit(effect)
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeFgsManagerController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeFgsManagerController.kt
index c33ce5d9484d..ced7955100f7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeFgsManagerController.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeFgsManagerController.kt
@@ -43,6 +43,9 @@ class FakeFgsManagerController(
override val showFooterDot: MutableStateFlow<Boolean> = MutableStateFlow(showFooterDot)
+ override var includesUserVisibleJobs = false
+ private set
+
private val numRunningPackagesListeners = LinkedHashSet<OnNumberOfPackagesChangedListener>()
private val dialogDismissedListeners = LinkedHashSet<OnDialogDismissedListener>()
@@ -74,7 +77,5 @@ class FakeFgsManagerController(
dialogDismissedListeners.remove(listener)
}
- override fun shouldUpdateFooterVisibility(): Boolean = false
-
override fun visibleButtonsCount(): Int = 0
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/DeviceConfigProxyFake.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/DeviceConfigProxyFake.java
index 21e16a1e7be4..8a10bf064910 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/DeviceConfigProxyFake.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/DeviceConfigProxyFake.java
@@ -81,11 +81,6 @@ public class DeviceConfigProxyFake extends DeviceConfigProxy {
properties.get(namespace).put(name, value);
}
- @Override
- public void enforceReadPermission(String namespace) {
- // no-op
- }
-
private Properties propsForNamespaceAndName(String namespace, String name) {
if (mProperties.containsKey(namespace) && mProperties.get(namespace).containsKey(name)) {
return new Properties.Builder(namespace)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/TestableAlertDialog.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/TestableAlertDialog.kt
new file mode 100644
index 000000000000..4d79554a79ce
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/TestableAlertDialog.kt
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.util
+
+import android.app.AlertDialog
+import android.content.Context
+import android.content.DialogInterface
+import java.lang.IllegalArgumentException
+
+/**
+ * [AlertDialog] that is easier to test. Due to [AlertDialog] being a class and not an interface,
+ * there are some things that cannot be avoided, like the creation of a [Handler] on the main thread
+ * (and therefore needing a prepared [Looper] in the test).
+ *
+ * It bypasses calls to show, clicks on buttons, cancel and dismiss so it all can happen bounded in
+ * the test. It tries to be as close in behavior as a real [AlertDialog].
+ *
+ * It will only call [onCreate] as part of its lifecycle, but not any of the other lifecycle methods
+ * in [Dialog].
+ *
+ * In order to test clicking on buttons, use [clickButton] instead of calling [View.callOnClick] on
+ * the view returned by [getButton] to bypass the internal [Handler].
+ */
+class TestableAlertDialog(context: Context) : AlertDialog(context) {
+
+ private var _onDismissListener: DialogInterface.OnDismissListener? = null
+ private var _onCancelListener: DialogInterface.OnCancelListener? = null
+ private var _positiveButtonClickListener: DialogInterface.OnClickListener? = null
+ private var _negativeButtonClickListener: DialogInterface.OnClickListener? = null
+ private var _neutralButtonClickListener: DialogInterface.OnClickListener? = null
+ private var _onShowListener: DialogInterface.OnShowListener? = null
+ private var _dismissOverride: Runnable? = null
+
+ private var showing = false
+ private var visible = false
+ private var created = false
+
+ override fun show() {
+ if (!created) {
+ created = true
+ onCreate(null)
+ }
+ if (isShowing) return
+ showing = true
+ visible = true
+ _onShowListener?.onShow(this)
+ }
+
+ override fun hide() {
+ visible = false
+ }
+
+ override fun isShowing(): Boolean {
+ return visible && showing
+ }
+
+ override fun dismiss() {
+ if (!showing) {
+ return
+ }
+ if (_dismissOverride != null) {
+ _dismissOverride?.run()
+ return
+ }
+ _onDismissListener?.onDismiss(this)
+ showing = false
+ }
+
+ override fun cancel() {
+ _onCancelListener?.onCancel(this)
+ dismiss()
+ }
+
+ override fun setOnDismissListener(listener: DialogInterface.OnDismissListener?) {
+ _onDismissListener = listener
+ }
+
+ override fun setOnCancelListener(listener: DialogInterface.OnCancelListener?) {
+ _onCancelListener = listener
+ }
+
+ override fun setOnShowListener(listener: DialogInterface.OnShowListener?) {
+ _onShowListener = listener
+ }
+
+ override fun takeCancelAndDismissListeners(
+ msg: String?,
+ cancel: DialogInterface.OnCancelListener?,
+ dismiss: DialogInterface.OnDismissListener?
+ ): Boolean {
+ _onCancelListener = cancel
+ _onDismissListener = dismiss
+ return true
+ }
+
+ override fun setButton(
+ whichButton: Int,
+ text: CharSequence?,
+ listener: DialogInterface.OnClickListener?
+ ) {
+ super.setButton(whichButton, text, listener)
+ when (whichButton) {
+ DialogInterface.BUTTON_POSITIVE -> _positiveButtonClickListener = listener
+ DialogInterface.BUTTON_NEGATIVE -> _negativeButtonClickListener = listener
+ DialogInterface.BUTTON_NEUTRAL -> _neutralButtonClickListener = listener
+ else -> Unit
+ }
+ }
+
+ /**
+ * Click one of the buttons in the [AlertDialog] and call the corresponding listener.
+ *
+ * Button ids are from [DialogInterface].
+ */
+ fun clickButton(whichButton: Int) {
+ val listener =
+ when (whichButton) {
+ DialogInterface.BUTTON_POSITIVE -> _positiveButtonClickListener
+ DialogInterface.BUTTON_NEGATIVE -> _negativeButtonClickListener
+ DialogInterface.BUTTON_NEUTRAL -> _neutralButtonClickListener
+ else -> throw IllegalArgumentException("Wrong button $whichButton")
+ }
+ listener?.onClick(this, whichButton)
+ dismiss()
+ }
+}
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
index b56818693124..52fb0a79a2bb 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
@@ -31,6 +31,7 @@ import com.android.systemui.unfold.updates.FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE
import com.android.systemui.unfold.updates.FoldStateProvider
import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdate
import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdatesListener
+import com.android.systemui.unfold.updates.name
/** Maps fold updates to unfold transition progress using DynamicAnimation. */
class PhysicsBasedUnfoldTransitionProgressProvider(
@@ -117,7 +118,7 @@ class PhysicsBasedUnfoldTransitionProgressProvider(
}
if (DEBUG) {
- Log.d(TAG, "onFoldUpdate = $update")
+ Log.d(TAG, "onFoldUpdate = ${update.name()}")
Trace.traceCounter(Trace.TRACE_TAG_APP, "fold_update", update)
}
}
diff --git a/packages/VpnDialogs/res/values-ar/strings.xml b/packages/VpnDialogs/res/values-ar/strings.xml
index 0e0581ef4cca..9307a744c37f 100644
--- a/packages/VpnDialogs/res/values-ar/strings.xml
+++ b/packages/VpnDialogs/res/values-ar/strings.xml
@@ -34,8 +34,6 @@
<string name="disconnect" msgid="971412338304200056">"قطع الاتصال"</string>
<string name="open_app" msgid="3717639178595958667">"فتح التطبيق"</string>
<string name="dismiss" msgid="6192859333764711227">"تجاهل"</string>
- <!-- no translation found for sanitized_vpn_label_with_ellipsis (7014327474633422235) -->
- <skip />
- <!-- no translation found for sanitized_vpn_label (1877415015009794766) -->
- <skip />
+ <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… ( <xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+ <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> ( <xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
</resources>
diff --git a/proto/src/OWNERS b/proto/src/OWNERS
index abd08deced79..ccff6245ff2c 100644
--- a/proto/src/OWNERS
+++ b/proto/src/OWNERS
@@ -2,3 +2,4 @@ per-file gnss.proto = file:/services/core/java/com/android/server/location/OWNER
per-file wifi.proto = file:/wifi/OWNERS
per-file camera.proto = file:/services/core/java/com/android/server/camera/OWNERS
per-file system_messages.proto = file:/core/res/OWNERS
+per-file altitude.proto = file:/location/OWNERS
diff --git a/proto/src/altitude.proto b/proto/src/altitude.proto
new file mode 100644
index 000000000000..1010f67b501d
--- /dev/null
+++ b/proto/src/altitude.proto
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+
+package com.android.internal.location.altitude;
+
+option java_package = "com.android.internal.location.altitude";
+option java_multiple_files = true;
+
+// Defines parameters for a spherically projected geoid map and corresponding
+// tile management.
+message MapParamsProto {
+ // Defines the resolution of the map in terms of an S2 level.
+ optional int32 map_s2_level = 1;
+ // Defines the resolution of the tiles in cache in terms of an S2 level.
+ optional int32 cache_tile_s2_level = 2;
+ // Defines the resolution of the tiles on disk in terms of an S2 level.
+ optional int32 disk_tile_s2_level = 3;
+ // Defines the `a` coefficient in the expression `a * map_value + b` used to
+ // calculate a geoid height in meters.
+ optional double model_a_meters = 4;
+ // Defines the `b` coefficient in the expression `a * map_value + b` used to
+ // calculate a geoid height in meters.
+ optional double model_b_meters = 5;
+ // Defines the root mean square error in meters of the geoid height.
+ optional double model_rmse_meters = 6;
+}
+
+// A single tile associating values in the unit interval [0, 1] to map cells.
+message S2TileProto {
+ // The S2 token associated with the common parent of all map cells in this
+ // tile.
+ optional string tile_key = 1;
+
+ // Encoded data that merge into a value in the unit interval [0, 1] for each
+ // map cell in this tile.
+ optional bytes byte_buffer = 2;
+ optional bytes byte_jpeg = 3;
+ optional bytes byte_png = 4;
+}
diff --git a/services/Android.bp b/services/Android.bp
index f6570e9d2702..3f3ba067632b 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -187,6 +187,7 @@ java_library {
"framework-tethering.stubs.module_lib",
"service-art.stubs.system_server",
"service-permission.stubs.system_server",
+ "service-rkp.stubs.system_server",
"service-sdksandbox.stubs.system_server",
],
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index 786d4076191e..e92150b895b0 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -74,8 +74,10 @@ import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.view.Display;
+import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.MagnificationSpec;
+import android.view.MotionEvent;
import android.view.SurfaceControl;
import android.view.View;
import android.view.WindowInfo;
@@ -200,6 +202,8 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
final ComponentName mComponentName;
+ int mGenericMotionEventSources;
+
// the events pending events to be dispatched to this service
final SparseArray<AccessibilityEvent> mPendingEvents = new SparseArray<>();
@@ -362,6 +366,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
}
mNotificationTimeout = info.notificationTimeout;
mIsDefault = (info.flags & DEFAULT) != 0;
+ mGenericMotionEventSources = info.getMotionEventSources();
if (supportsFlagForNotImportantViews(info)) {
if ((info.flags & AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS) != 0) {
@@ -1751,6 +1756,11 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
return mSystemSupport.getWindowTransformationMatrixAndMagnificationSpec(resolvedWindowId);
}
+ public boolean wantsGenericMotionEvent(MotionEvent event) {
+ final int eventSourceWithoutClass = event.getSource() & ~InputDevice.SOURCE_CLASS_MASK;
+ return (mGenericMotionEventSources & eventSourceWithoutClass) != 0;
+ }
+
/**
* Called by the invocation handler to notify the service that the
* state of magnification has changed.
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
index d80117d8d8ba..c87d1c8efafc 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -141,6 +141,9 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
*/
static final int FLAG_SEND_MOTION_EVENTS = 0x00000400;
+ /** Flag for intercepting generic motion events. */
+ static final int FLAG_FEATURE_INTERCEPT_GENERIC_MOTION_EVENTS = 0x00000800;
+
static final int FEATURES_AFFECTING_MOTION_EVENTS =
FLAG_FEATURE_INJECT_MOTION_EVENTS
| FLAG_FEATURE_AUTOCLICK
@@ -149,7 +152,8 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
| FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER
| FLAG_SERVICE_HANDLES_DOUBLE_TAP
| FLAG_REQUEST_MULTI_FINGER_GESTURES
- | FLAG_REQUEST_2_FINGER_PASSTHROUGH;
+ | FLAG_REQUEST_2_FINGER_PASSTHROUGH
+ | FLAG_FEATURE_INTERCEPT_GENERIC_MOTION_EVENTS;
private final Context mContext;
@@ -182,6 +186,10 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
private final SparseArray<EventStreamState> mTouchScreenStreamStates = new SparseArray<>(0);
+ // State tracking for generic MotionEvents is display-agnostic so we only need one.
+ private GenericMotionEventStreamState mGenericMotionEventStreamState;
+ private int mCombinedGenericMotionEventSources = 0;
+
private EventStreamState mKeyboardStreamState;
AccessibilityInputFilter(Context context, AccessibilityManagerService service) {
@@ -205,6 +213,7 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
mInstalled = true;
disableFeatures();
enableFeatures();
+ mAms.onInputFilterInstalled(true);
super.onInstalled();
}
@@ -215,6 +224,7 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
}
mInstalled = false;
disableFeatures();
+ mAms.onInputFilterInstalled(false);
super.onUninstalled();
}
@@ -296,7 +306,13 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
private EventStreamState getEventStreamState(InputEvent event) {
if (event instanceof MotionEvent) {
final int displayId = event.getDisplayId();
+ if (mGenericMotionEventStreamState == null) {
+ mGenericMotionEventStreamState = new GenericMotionEventStreamState();
+ }
+ if (mGenericMotionEventStreamState.shouldProcessMotionEvent((MotionEvent) event)) {
+ return mGenericMotionEventStreamState;
+ }
if (event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN)) {
EventStreamState touchScreenStreamState = mTouchScreenStreamStates.get(displayId);
if (touchScreenStreamState == null) {
@@ -362,8 +378,11 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
mPm.userActivity(event.getEventTime(), false);
MotionEvent transformedEvent = MotionEvent.obtain(event);
final int displayId = event.getDisplayId();
- mEventHandler.get(isDisplayIdValid(displayId) ? displayId : Display.DEFAULT_DISPLAY)
- .onMotionEvent(transformedEvent, event, policyFlags);
+ EventStreamTransformation eventStreamTransformation = mEventHandler.get(
+ isDisplayIdValid(displayId) ? displayId : Display.DEFAULT_DISPLAY);
+ if (eventStreamTransformation != null) {
+ eventStreamTransformation.onMotionEvent(transformedEvent, event, policyFlags);
+ }
transformedEvent.recycle();
}
@@ -490,6 +509,19 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
mTouchExplorer.put(displayId, explorer);
}
+ if ((mEnabledFeatures & FLAG_FEATURE_INTERCEPT_GENERIC_MOTION_EVENTS) != 0) {
+ addFirstEventHandler(displayId, new BaseEventStreamTransformation() {
+ @Override
+ public void onMotionEvent(MotionEvent event, MotionEvent rawEvent,
+ int policyFlags) {
+ if (!anyServiceWantsGenericMotionEvent(rawEvent)
+ || !mAms.sendMotionEventToListeningServices(rawEvent)) {
+ super.onMotionEvent(event, rawEvent, policyFlags);
+ }
+ }
+ });
+ }
+
if ((mEnabledFeatures & FLAG_FEATURE_CONTROL_SCREEN_MAGNIFIER) != 0
|| ((mEnabledFeatures & FLAG_FEATURE_SCREEN_MAGNIFIER) != 0)
|| ((mEnabledFeatures & FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER) != 0)) {
@@ -842,6 +874,32 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
}
}
+ private class GenericMotionEventStreamState extends EventStreamState {
+ @Override
+ public boolean shouldProcessMotionEvent(MotionEvent event) {
+ return anyServiceWantsGenericMotionEvent(event);
+ }
+ @Override
+ public boolean shouldProcessScroll() {
+ return true;
+ }
+ }
+
+ private boolean anyServiceWantsGenericMotionEvent(MotionEvent event) {
+ // Disable SOURCE_TOUCHSCREEN generic event interception if any service is performing
+ // touch exploration.
+ if (event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN)
+ && (mEnabledFeatures & FLAG_FEATURE_TOUCH_EXPLORATION) != 0) {
+ return false;
+ }
+ final int eventSourceWithoutClass = event.getSource() & ~InputDevice.SOURCE_CLASS_MASK;
+ return (mCombinedGenericMotionEventSources & eventSourceWithoutClass) != 0;
+ }
+
+ public void setCombinedGenericMotionEventSources(int sources) {
+ mCombinedGenericMotionEventSources = sources;
+ }
+
/**
* Keeps state of streams of events from all keyboard devices.
*/
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 630cd35029bd..3145139cc6bb 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -106,6 +106,8 @@ import android.util.Slog;
import android.util.SparseArray;
import android.view.Display;
import android.view.IWindow;
+import android.view.InputDevice;
+import android.view.InputEvent;
import android.view.KeyEvent;
import android.view.MagnificationSpec;
import android.view.MotionEvent;
@@ -147,6 +149,7 @@ import com.android.server.accessibility.magnification.MagnificationScaleProvider
import com.android.server.accessibility.magnification.WindowMagnificationManager;
import com.android.server.inputmethod.InputMethodManagerInternal;
import com.android.server.pm.UserManagerInternal;
+import com.android.server.policy.WindowManagerPolicy;
import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.WindowManagerInternal;
@@ -190,7 +193,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
// TODO: Restructure service initialization so services aren't connected before all of
// their capabilities are ready.
- private static final int WAIT_MOTION_INJECTOR_TIMEOUT_MILLIS = 1000;
+ private static final int WAIT_INPUT_FILTER_INSTALL_TIMEOUT_MS = 1000;
// This postpones state changes events when a window doesn't exist with the expectation that
@@ -258,6 +261,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
private boolean mHasInputFilter;
+ private boolean mInputFilterInstalled;
+
private KeyEventDispatcher mKeyEventDispatcher;
private SparseArray<MotionEventInjector> mMotionEventInjectors;
@@ -517,6 +522,16 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
return mFingerprintGestureDispatcher;
}
+ /**
+ * Called by the {@link AccessibilityInputFilter} when the filter install state changes.
+ */
+ public void onInputFilterInstalled(boolean installed) {
+ synchronized (mLock) {
+ mInputFilterInstalled = installed;
+ mLock.notifyAll();
+ }
+ }
+
private void onBootPhase(int phase) {
if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_APP_WIDGETS)) {
@@ -1350,7 +1365,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
}
- /** Send a motion event to the service to allow it to perform gesture detection. */
+ /** Send a motion event to the services. */
public boolean sendMotionEventToListeningServices(MotionEvent event) {
boolean result;
event = MotionEvent.obtain(event);
@@ -1453,7 +1468,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
@Override
public @Nullable MotionEventInjector getMotionEventInjectorForDisplayLocked(int displayId) {
- final long endMillis = SystemClock.uptimeMillis() + WAIT_MOTION_INJECTOR_TIMEOUT_MILLIS;
+ final long endMillis = SystemClock.uptimeMillis() + WAIT_INPUT_FILTER_INSTALL_TIMEOUT_MS;
MotionEventInjector motionEventInjector = null;
while ((mMotionEventInjectors == null) && (SystemClock.uptimeMillis() < endMillis)) {
try {
@@ -1707,7 +1722,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
AccessibilityUserState state = getCurrentUserStateLocked();
for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
AccessibilityServiceConnection service = state.mBoundServices.get(i);
- if (service.isServiceDetectsGesturesEnabled(displayId)) {
+ if (service.wantsGenericMotionEvent(event)
+ || (event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN)
+ && service.isServiceDetectsGesturesEnabled(displayId))) {
service.notifyMotionEvent(event);
result = true;
}
@@ -2302,6 +2319,13 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
if (userState.isPerformGesturesEnabledLocked()) {
flags |= AccessibilityInputFilter.FLAG_FEATURE_INJECT_MOTION_EVENTS;
}
+ int combinedGenericMotionEventSources = 0;
+ for (AccessibilityServiceConnection connection : userState.mBoundServices) {
+ combinedGenericMotionEventSources |= connection.mGenericMotionEventSources;
+ }
+ if (combinedGenericMotionEventSources != 0) {
+ flags |= AccessibilityInputFilter.FLAG_FEATURE_INTERCEPT_GENERIC_MOTION_EVENTS;
+ }
if (flags != 0) {
if (!mHasInputFilter) {
mHasInputFilter = true;
@@ -2314,6 +2338,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
setInputFilter = true;
}
mInputFilter.setUserAndEnabledFeatures(userState.mUserId, flags);
+ mInputFilter.setCombinedGenericMotionEventSources(
+ combinedGenericMotionEventSources);
} else {
if (mHasInputFilter) {
mHasInputFilter = false;
@@ -4646,6 +4672,29 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
}
+ @Override
+ public void injectInputEventToInputFilter(InputEvent event) {
+ synchronized (mLock) {
+ final long endMillis =
+ SystemClock.uptimeMillis() + WAIT_INPUT_FILTER_INSTALL_TIMEOUT_MS;
+ while (!mInputFilterInstalled && (SystemClock.uptimeMillis() < endMillis)) {
+ try {
+ mLock.wait(endMillis - SystemClock.uptimeMillis());
+ } catch (InterruptedException ie) {
+ /* ignore */
+ }
+ }
+ }
+
+ if (mInputFilterInstalled && mInputFilter != null) {
+ mInputFilter.onInputEvent(event,
+ WindowManagerPolicy.FLAG_PASS_TO_USER | WindowManagerPolicy.FLAG_INJECTED);
+ } else {
+ Slog.w(LOG_TAG, "Cannot injectInputEventToInputFilter because the "
+ + "AccessibilityInputFilter is not installed.");
+ }
+ }
+
private final class SendWindowStateChangedEventRunnable implements Runnable {
private final AccessibilityEvent mPendingEvent;
diff --git a/services/api/current.txt b/services/api/current.txt
index 834ed2f67865..da5b1fcaae99 100644
--- a/services/api/current.txt
+++ b/services/api/current.txt
@@ -57,8 +57,8 @@ package com.android.server.pm {
public static interface PackageManagerLocal.FilteredSnapshot extends java.lang.AutoCloseable {
method public void close();
- method public void forAllPackageStates(@NonNull java.util.function.Consumer<com.android.server.pm.pkg.PackageState>);
method @Nullable public com.android.server.pm.pkg.PackageState getPackageState(@NonNull String);
+ method @NonNull public java.util.Map<java.lang.String,com.android.server.pm.pkg.PackageState> getPackageStates();
}
public static interface PackageManagerLocal.UnfilteredSnapshot extends java.lang.AutoCloseable {
@@ -98,6 +98,7 @@ package com.android.server.pm.pkg {
public interface PackageState {
method @Nullable public com.android.server.pm.pkg.AndroidPackage getAndroidPackage();
method public int getAppId();
+ method public int getHiddenApiEnforcementPolicy();
method @NonNull public String getPackageName();
method @Nullable public String getPrimaryCpuAbi();
method @Nullable public String getSeInfo();
@@ -128,13 +129,6 @@ package com.android.server.pm.pkg {
}
-package com.android.server.pm.snapshot {
-
- public interface PackageDataSnapshot {
- }
-
-}
-
package com.android.server.role {
public interface RoleServicePlatformHelper {
diff --git a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
index 28141960f119..5e68d5231c6b 100644
--- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
+++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
@@ -16,8 +16,7 @@
package com.android.server.companion.virtual;
-import static android.companion.AssociationRequest.DEVICE_PROFILE_APP_STREAMING;
-import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION;
+import static android.companion.virtual.VirtualDeviceParams.RECENTS_POLICY_ALLOW_IN_HOST_DEVICE_RECENTS;
import static android.content.pm.ActivityInfo.FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES;
import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
@@ -26,10 +25,10 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.WindowConfiguration;
import android.app.compat.CompatChanges;
-import android.companion.AssociationRequest;
import android.companion.virtual.VirtualDeviceManager.ActivityListener;
import android.companion.virtual.VirtualDeviceParams;
import android.companion.virtual.VirtualDeviceParams.ActivityPolicy;
+import android.companion.virtual.VirtualDeviceParams.RecentsPolicy;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
import android.content.ComponentName;
@@ -127,10 +126,10 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController
@GuardedBy("mGenericWindowPolicyControllerLock")
private final ArraySet<RunningAppsChangedListener> mRunningAppsChangedListeners =
new ArraySet<>();
- @Nullable
- private final @AssociationRequest.DeviceProfile String mDeviceProfile;
@Nullable private final SecureWindowCallback mSecureWindowCallback;
@Nullable private final List<String> mDisplayCategories;
+ @RecentsPolicy
+ private final int mDefaultRecentsPolicy;
/**
* Creates a window policy controller that is generic to the different use cases of virtual
@@ -156,7 +155,7 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController
* launching.
* @param secureWindowCallback Callback that is called when a secure window shows on the
* virtual display.
- * @param deviceProfile The {@link AssociationRequest.DeviceProfile} of this virtual device.
+ * @param defaultRecentsPolicy a policy to indicate how to handle activities in recents.
*/
public GenericWindowPolicyController(int windowFlags, int systemWindowFlags,
@NonNull ArraySet<UserHandle> allowedUsers,
@@ -169,8 +168,8 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController
@NonNull PipBlockedCallback pipBlockedCallback,
@NonNull ActivityBlockedCallback activityBlockedCallback,
@NonNull SecureWindowCallback secureWindowCallback,
- @AssociationRequest.DeviceProfile String deviceProfile,
- @NonNull List<String> displayCategories) {
+ @NonNull List<String> displayCategories,
+ @RecentsPolicy int defaultRecentsPolicy) {
super();
mAllowedUsers = allowedUsers;
mAllowedCrossTaskNavigations = new ArraySet<>(allowedCrossTaskNavigations);
@@ -181,10 +180,10 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController
mActivityBlockedCallback = activityBlockedCallback;
setInterestedWindowFlags(windowFlags, systemWindowFlags);
mActivityListener = activityListener;
- mDeviceProfile = deviceProfile;
mPipBlockedCallback = pipBlockedCallback;
mSecureWindowCallback = secureWindowCallback;
mDisplayCategories = displayCategories;
+ mDefaultRecentsPolicy = defaultRecentsPolicy;
}
/**
@@ -318,18 +317,8 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController
}
@Override
- public boolean canShowTasksInRecents() {
- if (mDeviceProfile == null) {
- return true;
- }
- // TODO(b/234075973) : Remove this once proper API is ready.
- switch (mDeviceProfile) {
- case DEVICE_PROFILE_AUTOMOTIVE_PROJECTION:
- return false;
- case DEVICE_PROFILE_APP_STREAMING:
- default:
- return true;
- }
+ public boolean canShowTasksInHostDeviceRecents() {
+ return (mDefaultRecentsPolicy & RECENTS_POLICY_ALLOW_IN_HOST_DEVICE_RECENTS) != 0;
}
@Override
@@ -355,10 +344,10 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController
private boolean activityMatchesDisplayCategory(ActivityInfo activityInfo) {
if (mDisplayCategories.isEmpty()) {
- return activityInfo.targetDisplayCategory == null;
+ return activityInfo.requiredDisplayCategory == null;
}
- return activityInfo.targetDisplayCategory != null
- && mDisplayCategories.contains(activityInfo.targetDisplayCategory);
+ return activityInfo.requiredDisplayCategory != null
+ && mDisplayCategories.contains(activityInfo.requiredDisplayCategory);
}
@@ -375,9 +364,9 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController
}
if (!activityMatchesDisplayCategory(activityInfo)) {
Slog.d(TAG, String.format(
- "The activity's target display category: %s is not found on virtual display"
- + " with the following allowed display categories: %s",
- activityInfo.targetDisplayCategory, mDisplayCategories.toString()));
+ "The activity's required display category: %s is not found on virtual display"
+ + " with the following categories: %s",
+ activityInfo.requiredDisplayCategory, mDisplayCategories.toString()));
return false;
}
final UserHandle activityUser =
diff --git a/services/companion/java/com/android/server/companion/virtual/InputController.java b/services/companion/java/com/android/server/companion/virtual/InputController.java
index 02053cc7cfd3..0cea3d0575c6 100644
--- a/services/companion/java/com/android/server/companion/virtual/InputController.java
+++ b/services/companion/java/com/android/server/companion/virtual/InputController.java
@@ -19,7 +19,6 @@ package com.android.server.companion.virtual;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.StringDef;
-import android.graphics.Point;
import android.graphics.PointF;
import android.hardware.display.DisplayManagerInternal;
import android.hardware.input.InputDeviceIdentifier;
@@ -79,9 +78,8 @@ class InputController {
final Object mLock;
/* Token -> file descriptor associations. */
- @VisibleForTesting
@GuardedBy("mLock")
- final Map<IBinder, InputDeviceDescriptor> mInputDeviceDescriptors = new ArrayMap<>();
+ private final Map<IBinder, InputDeviceDescriptor> mInputDeviceDescriptors = new ArrayMap<>();
private final Handler mHandler;
private final NativeWrapper mNativeWrapper;
@@ -178,13 +176,14 @@ class InputController {
int productId,
@NonNull IBinder deviceToken,
int displayId,
- @NonNull Point screenSize) {
+ int height,
+ int width) {
final String phys = createPhys(PHYS_TYPE_TOUCHSCREEN);
try {
createDeviceInternal(InputDeviceDescriptor.TYPE_TOUCHSCREEN, deviceName, vendorId,
productId, deviceToken, displayId, phys,
() -> mNativeWrapper.openUinputTouchscreen(deviceName, vendorId, productId,
- phys, screenSize.y, screenSize.x));
+ phys, height, width));
} catch (DeviceCreationException e) {
throw new RuntimeException(
"Failed to create virtual touchscreen device '" + deviceName + "'.", e);
@@ -414,17 +413,25 @@ class InputController {
}
@VisibleForTesting
- void addDeviceForTesting(IBinder deviceToken, int fd, int type, int displayId,
- String phys, int inputDeviceId) {
+ void addDeviceForTesting(IBinder deviceToken, int fd, int type, int displayId, String phys,
+ int inputDeviceId) {
synchronized (mLock) {
- mInputDeviceDescriptors.put(deviceToken,
- new InputDeviceDescriptor(fd, () -> {}, type, displayId, phys,
- inputDeviceId));
+ mInputDeviceDescriptors.put(deviceToken, new InputDeviceDescriptor(fd, () -> {
+ }, type, displayId, phys, inputDeviceId));
}
}
- private static native int nativeOpenUinputDpad(String deviceName, int vendorId,
- int productId, String phys);
+ @VisibleForTesting
+ Map<IBinder, InputDeviceDescriptor> getInputDeviceDescriptors() {
+ final Map<IBinder, InputDeviceDescriptor> inputDeviceDescriptors = new ArrayMap<>();
+ synchronized (mLock) {
+ inputDeviceDescriptors.putAll(mInputDeviceDescriptors);
+ }
+ return inputDeviceDescriptors;
+ }
+
+ private static native int nativeOpenUinputDpad(String deviceName, int vendorId, int productId,
+ String phys);
private static native int nativeOpenUinputKeyboard(String deviceName, int vendorId,
int productId, String phys);
private static native int nativeOpenUinputMouse(String deviceName, int vendorId, int productId,
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index 828f302e631a..58198612d80e 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -44,14 +44,17 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
-import android.graphics.Point;
import android.graphics.PointF;
import android.hardware.display.DisplayManager;
+import android.hardware.input.VirtualDpadConfig;
import android.hardware.input.VirtualKeyEvent;
+import android.hardware.input.VirtualKeyboardConfig;
import android.hardware.input.VirtualMouseButtonEvent;
+import android.hardware.input.VirtualMouseConfig;
import android.hardware.input.VirtualMouseRelativeEvent;
import android.hardware.input.VirtualMouseScrollEvent;
import android.hardware.input.VirtualTouchEvent;
+import android.hardware.input.VirtualTouchscreenConfig;
import android.os.Binder;
import android.os.IBinder;
import android.os.Looper;
@@ -397,19 +400,12 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
}
}
- @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
@Override // Binder call
- public void createVirtualDpad(
- int displayId,
- @NonNull String deviceName,
- int vendorId,
- int productId,
- @NonNull IBinder deviceToken) {
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
+ public void createVirtualDpad(VirtualDpadConfig config, @NonNull IBinder deviceToken) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
"Permission required to create a virtual dpad");
synchronized (mVirtualDeviceLock) {
- if (!mVirtualDisplayIds.contains(displayId)) {
+ if (!mVirtualDisplayIds.contains(config.getAssociatedDisplayId())) {
throw new SecurityException(
"Cannot create a virtual dpad for a display not associated with "
+ "this virtual device");
@@ -417,26 +413,19 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
}
final long ident = Binder.clearCallingIdentity();
try {
- mInputController.createDpad(deviceName, vendorId, productId, deviceToken,
- displayId);
+ mInputController.createDpad(config.getInputDeviceName(), config.getVendorId(),
+ config.getProductId(), deviceToken, config.getAssociatedDisplayId());
} finally {
Binder.restoreCallingIdentity(ident);
}
}
- @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
@Override // Binder call
- public void createVirtualKeyboard(
- int displayId,
- @NonNull String deviceName,
- int vendorId,
- int productId,
- @NonNull IBinder deviceToken) {
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
+ public void createVirtualKeyboard(VirtualKeyboardConfig config, @NonNull IBinder deviceToken) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
"Permission required to create a virtual keyboard");
synchronized (mVirtualDeviceLock) {
- if (!mVirtualDisplayIds.contains(displayId)) {
+ if (!mVirtualDisplayIds.contains(config.getAssociatedDisplayId())) {
throw new SecurityException(
"Cannot create a virtual keyboard for a display not associated with "
+ "this virtual device");
@@ -444,26 +433,19 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
}
final long ident = Binder.clearCallingIdentity();
try {
- mInputController.createKeyboard(deviceName, vendorId, productId, deviceToken,
- displayId);
+ mInputController.createKeyboard(config.getInputDeviceName(), config.getVendorId(),
+ config.getProductId(), deviceToken, config.getAssociatedDisplayId());
} finally {
Binder.restoreCallingIdentity(ident);
}
}
- @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
@Override // Binder call
- public void createVirtualMouse(
- int displayId,
- @NonNull String deviceName,
- int vendorId,
- int productId,
- @NonNull IBinder deviceToken) {
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
+ public void createVirtualMouse(VirtualMouseConfig config, @NonNull IBinder deviceToken) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
"Permission required to create a virtual mouse");
synchronized (mVirtualDeviceLock) {
- if (!mVirtualDisplayIds.contains(displayId)) {
+ if (!mVirtualDisplayIds.contains(config.getAssociatedDisplayId())) {
throw new SecurityException(
"Cannot create a virtual mouse for a display not associated with this "
+ "virtual device");
@@ -471,42 +453,38 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
}
final long ident = Binder.clearCallingIdentity();
try {
- mInputController.createMouse(deviceName, vendorId, productId, deviceToken, displayId);
+ mInputController.createMouse(config.getInputDeviceName(), config.getVendorId(),
+ config.getProductId(), deviceToken, config.getAssociatedDisplayId());
} finally {
Binder.restoreCallingIdentity(ident);
}
}
- @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
@Override // Binder call
- public void createVirtualTouchscreen(
- int displayId,
- @NonNull String deviceName,
- int vendorId,
- int productId,
- @NonNull IBinder deviceToken,
- @NonNull Point screenSize) {
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
+ public void createVirtualTouchscreen(VirtualTouchscreenConfig config,
+ @NonNull IBinder deviceToken) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
"Permission required to create a virtual touchscreen");
synchronized (mVirtualDeviceLock) {
- if (!mVirtualDisplayIds.contains(displayId)) {
+ if (!mVirtualDisplayIds.contains(config.getAssociatedDisplayId())) {
throw new SecurityException(
"Cannot create a virtual touchscreen for a display not associated with "
+ "this virtual device");
}
}
-
- if (screenSize.x <= 0 || screenSize.y <= 0) {
+ int screenHeightPixels = config.getHeightInPixels();
+ int screenWidthPixels = config.getWidthInPixels();
+ if (screenHeightPixels <= 0 || screenWidthPixels <= 0) {
throw new IllegalArgumentException(
"Cannot create a virtual touchscreen, screen dimensions must be positive. Got: "
- + screenSize);
+ + "(" + screenWidthPixels + ", " + screenHeightPixels + ")");
}
final long ident = Binder.clearCallingIdentity();
try {
- mInputController.createTouchscreen(deviceName, vendorId, productId,
- deviceToken, displayId, screenSize);
+ mInputController.createTouchscreen(config.getInputDeviceName(), config.getVendorId(),
+ config.getProductId(), deviceToken, config.getAssociatedDisplayId(),
+ screenHeightPixels, screenWidthPixels);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -701,8 +679,8 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
this::onEnteringPipBlocked,
this::onActivityBlocked,
this::onSecureWindowShown,
- mAssociationInfo.getDeviceProfile(),
- displayCategories);
+ displayCategories,
+ mParams.getDefaultRecentsPolicy());
gwpc.registerRunningAppsChangedListener(/* listener= */ this);
return gwpc;
}
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index 2b62f69d78f3..48ca0b3abef6 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -47,6 +47,7 @@ import android.util.ArraySet;
import android.util.ExceptionUtils;
import android.util.Slog;
import android.util.SparseArray;
+import android.view.Display;
import android.widget.Toast;
import com.android.internal.annotations.GuardedBy;
@@ -388,6 +389,23 @@ public class VirtualDeviceManagerService extends SystemService {
return mLocalService.getDevicePolicy(deviceId, policyType);
}
+
+ @Override // Binder call
+ public int getDeviceIdForDisplayId(int displayId) {
+ if (displayId == Display.INVALID_DISPLAY || displayId == Display.DEFAULT_DISPLAY) {
+ return VirtualDeviceManager.DEFAULT_DEVICE_ID;
+ }
+ synchronized (mVirtualDeviceManagerLock) {
+ for (int i = 0; i < mVirtualDevices.size(); i++) {
+ VirtualDeviceImpl virtualDevice = mVirtualDevices.valueAt(i);
+ if (virtualDevice.isDisplayOwnedByVirtualDevice(displayId)) {
+ return virtualDevice.getDeviceId();
+ }
+ }
+ }
+ return VirtualDeviceManager.DEFAULT_DEVICE_ID;
+ }
+
@Nullable
private AssociationInfo getAssociationInfo(String packageName, int associationId) {
final int callingUserId = getCallingUserHandle().getIdentifier();
diff --git a/services/core/Android.bp b/services/core/Android.bp
index a61a61b4f5c7..1e1d61050a5d 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -132,6 +132,7 @@ java_library_static {
"framework-tethering.stubs.module_lib",
"service-art.stubs.system_server",
"service-permission.stubs.system_server",
+ "service-rkp.stubs.system_server",
"service-sdksandbox.stubs.system_server",
],
plugins: ["ImmutabilityAnnotationProcessor"],
@@ -144,6 +145,7 @@ java_library_static {
static_libs: [
"android.hardware.authsecret-V1.0-java",
+ "android.hardware.authsecret-V1-java",
"android.hardware.boot-V1.0-java",
"android.hardware.boot-V1.1-java",
"android.hardware.boot-V1.2-java",
diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java
index 6cd7ce8a1c43..6f0971cfc0ce 100644
--- a/services/core/java/com/android/server/BinaryTransparencyService.java
+++ b/services/core/java/com/android/server/BinaryTransparencyService.java
@@ -476,6 +476,7 @@ public class BinaryTransparencyService extends SystemService {
}
private void printPackageMeasurements(PackageInfo packageInfo,
+ boolean useSha256,
final PrintWriter pw) {
Map<Integer, byte[]> contentDigests = computeApkContentDigest(
packageInfo.applicationInfo.sourceDir);
@@ -485,6 +486,14 @@ public class BinaryTransparencyService extends SystemService {
return;
}
+ if (useSha256) {
+ byte[] fileBuff = PackageUtils.createLargeFileBuffer();
+ String hexEncodedSha256Digest =
+ PackageUtils.computeSha256DigestForLargeFile(
+ packageInfo.applicationInfo.sourceDir, fileBuff);
+ pw.print(hexEncodedSha256Digest + ",");
+ }
+
for (Map.Entry<Integer, byte[]> entry : contentDigests.entrySet()) {
Integer algorithmId = entry.getKey();
byte[] contentDigest = entry.getValue();
@@ -497,6 +506,7 @@ public class BinaryTransparencyService extends SystemService {
}
private void printPackageInstallationInfo(PackageInfo packageInfo,
+ boolean useSha256,
final PrintWriter pw) {
pw.println("--- Package Installation Info ---");
pw.println("Current install location: "
@@ -507,11 +517,13 @@ public class BinaryTransparencyService extends SystemService {
pw.println("|--> Pre-installed package install location: "
+ origPackageFilepath);
- // TODO(b/259347186): revive this with the proper cmd options.
- /*
- String digest = PackageUtils.computeSha256DigestForLargeFile(
- origPackageFilepath, PackageUtils.createLargeFileBuffer());
- */
+ if (useSha256) {
+ String sha256Digest = PackageUtils.computeSha256DigestForLargeFile(
+ origPackageFilepath, PackageUtils.createLargeFileBuffer());
+ pw.println("|--> Pre-installed package SHA-256 digest: "
+ + sha256Digest);
+ }
+
Map<Integer, byte[]> contentDigests = computeApkContentDigest(
origPackageFilepath);
@@ -531,6 +543,8 @@ public class BinaryTransparencyService extends SystemService {
}
pw.println("First install time (ms): " + packageInfo.firstInstallTime);
pw.println("Last update time (ms): " + packageInfo.lastUpdateTime);
+ // TODO(b/261493591): Determination of whether a package is preinstalled can be
+ // made more robust
boolean isPreloaded = (packageInfo.firstInstallTime
== packageInfo.lastUpdateTime);
pw.println("Is preloaded: " + isPreloaded);
@@ -560,6 +574,7 @@ public class BinaryTransparencyService extends SystemService {
pw.println("ERROR: Package's signingInfo is null.");
return;
}
+ // TODO(b/261501773): Handle printing of lineage of rotated keys.
pw.println("--- Package Signer Info ---");
pw.println("Has multiple signers: " + signerInfo.hasMultipleSigners());
Signature[] packageSigners = signerInfo.getApkContentsSigners();
@@ -669,15 +684,35 @@ public class BinaryTransparencyService extends SystemService {
}
+ private void printHeadersHelper(@NonNull String packageType,
+ boolean useSha256,
+ @NonNull final PrintWriter pw) {
+ pw.print(packageType + " Info [Format: package_name,package_version,");
+ if (useSha256) {
+ pw.print("package_sha256_digest,");
+ }
+ pw.print("content_digest_algorithm:content_digest]:\n");
+ }
+
private int printAllApexs() {
final PrintWriter pw = getOutPrintWriter();
boolean verbose = false;
+ boolean useSha256 = false;
+ boolean printHeaders = true;
String opt;
while ((opt = getNextOption()) != null) {
switch (opt) {
case "-v":
+ case "--verbose":
verbose = true;
break;
+ case "-o":
+ case "--old":
+ useSha256 = true;
+ break;
+ case "--no-headers":
+ printHeaders = false;
+ break;
default:
pw.println("ERROR: Unknown option: " + opt);
return 1;
@@ -690,23 +725,17 @@ public class BinaryTransparencyService extends SystemService {
return -1;
}
- if (!verbose) {
- pw.println("APEX Info [Format: package_name,package_version,"
- // TODO(b/259347186): revive via special cmd line option
- //+ "package_sha256_digest,"
- + "content_digest_algorithm:content_digest]:");
+ if (!verbose && printHeaders) {
+ printHeadersHelper("APEX", useSha256, pw);
}
for (PackageInfo packageInfo : getCurrentInstalledApexs()) {
- if (verbose) {
- pw.println("APEX Info [Format: package_name,package_version,"
- // TODO(b/259347186): revive via special cmd line option
- //+ "package_sha256_digest,"
- + "content_digest_algorithm:content_digest]:");
+ if (verbose && printHeaders) {
+ printHeadersHelper("APEX", useSha256, pw);
}
String packageName = packageInfo.packageName;
pw.print(packageName + ","
+ packageInfo.getLongVersionCode() + ",");
- printPackageMeasurements(packageInfo, pw);
+ printPackageMeasurements(packageInfo, useSha256, pw);
if (verbose) {
ModuleInfo moduleInfo;
@@ -718,7 +747,7 @@ public class BinaryTransparencyService extends SystemService {
pw.println("Is a module: false");
}
- printPackageInstallationInfo(packageInfo, pw);
+ printPackageInstallationInfo(packageInfo, useSha256, pw);
printPackageSignerDetails(packageInfo.signingInfo, pw);
pw.println("");
}
@@ -729,12 +758,22 @@ public class BinaryTransparencyService extends SystemService {
private int printAllModules() {
final PrintWriter pw = getOutPrintWriter();
boolean verbose = false;
+ boolean useSha256 = false;
+ boolean printHeaders = true;
String opt;
while ((opt = getNextOption()) != null) {
switch (opt) {
case "-v":
+ case "--verbose":
verbose = true;
break;
+ case "-o":
+ case "--old":
+ useSha256 = true;
+ break;
+ case "--no-headers":
+ printHeaders = false;
+ break;
default:
pw.println("ERROR: Unknown option: " + opt);
return 1;
@@ -747,32 +786,25 @@ public class BinaryTransparencyService extends SystemService {
return -1;
}
- if (!verbose) {
- pw.println("Module Info [Format: package_name,package_version,"
- // TODO(b/259347186): revive via special cmd line option
- //+ "package_sha256_digest,"
- + "content_digest_algorithm:content_digest]:");
+ if (!verbose && printHeaders) {
+ printHeadersHelper("Module", useSha256, pw);
}
for (ModuleInfo module : pm.getInstalledModules(PackageManager.MATCH_ALL)) {
String packageName = module.getPackageName();
- if (verbose) {
- pw.println("Module Info [Format: package_name,package_version,"
- // TODO(b/259347186): revive via special cmd line option
- //+ "package_sha256_digest,"
- + "content_digest_algorithm:content_digest]:");
+ if (verbose && printHeaders) {
+ printHeadersHelper("Module", useSha256, pw);
}
try {
PackageInfo packageInfo = pm.getPackageInfo(packageName,
PackageManager.MATCH_APEX
| PackageManager.GET_SIGNING_CERTIFICATES);
- //pw.print("package:");
pw.print(packageInfo.packageName + ",");
pw.print(packageInfo.getLongVersionCode() + ",");
- printPackageMeasurements(packageInfo, pw);
+ printPackageMeasurements(packageInfo, useSha256, pw);
if (verbose) {
printModuleDetails(module, pw);
- printPackageInstallationInfo(packageInfo, pw);
+ printPackageInstallationInfo(packageInfo, useSha256, pw);
printPackageSignerDetails(packageInfo.signingInfo, pw);
pw.println("");
}
@@ -793,41 +825,45 @@ public class BinaryTransparencyService extends SystemService {
final PrintWriter pw = getOutPrintWriter();
boolean verbose = false;
boolean printLibraries = false;
+ boolean useSha256 = false;
+ boolean printHeaders = true;
String opt;
while ((opt = getNextOption()) != null) {
switch (opt) {
case "-v":
+ case "--verbose":
verbose = true;
break;
case "-l":
printLibraries = true;
break;
+ case "-o":
+ case "--old":
+ useSha256 = true;
+ break;
+ case "--no-headers":
+ printHeaders = false;
+ break;
default:
pw.println("ERROR: Unknown option: " + opt);
return 1;
}
}
- if (!verbose) {
- pw.println("MBA Info [Format: package_name,package_version,"
- // TODO(b/259347186): revive via special cmd line option
- //+ "package_sha256_digest,"
- + "content_digest_algorithm:content_digest]:");
+ if (!verbose && printHeaders) {
+ printHeadersHelper("MBA", useSha256, pw);
}
for (PackageInfo packageInfo : getNewlyInstalledMbas()) {
- if (verbose) {
- pw.println("MBA Info [Format: package_name,package_version,"
- // TODO(b/259347186): revive via special cmd line option
- //+ "package_sha256_digest,"
- + "content_digest_algorithm:content_digest]:");
+ if (verbose && printHeaders) {
+ printHeadersHelper("MBA", useSha256, pw);
}
pw.print(packageInfo.packageName + ",");
pw.print(packageInfo.getLongVersionCode() + ",");
- printPackageMeasurements(packageInfo, pw);
+ printPackageMeasurements(packageInfo, useSha256, pw);
if (verbose) {
printAppDetails(packageInfo, printLibraries, pw);
- printPackageInstallationInfo(packageInfo, pw);
+ printPackageInstallationInfo(packageInfo, useSha256, pw);
printPackageSignerDetails(packageInfo.signingInfo, pw);
pw.println("");
}
@@ -894,27 +930,39 @@ public class BinaryTransparencyService extends SystemService {
private void printHelpMenu() {
final PrintWriter pw = getOutPrintWriter();
pw.println("Transparency manager (transparency) commands:");
- pw.println(" help");
- pw.println(" Print this help text.");
+ pw.println(" help");
+ pw.println(" Print this help text.");
pw.println("");
- pw.println(" get image_info [-a]");
- pw.println(" Print information about loaded image (firmware). Options:");
- pw.println(" -a: lists all other identifiable partitions.");
+ pw.println(" get image_info [-a]");
+ pw.println(" Print information about loaded image (firmware). Options:");
+ pw.println(" -a: lists all other identifiable partitions.");
pw.println("");
- pw.println(" get apex_info [-v]");
- pw.println(" Print information about installed APEXs on device.");
- pw.println(" -v: lists more verbose information about each APEX.");
+ pw.println(" get apex_info [-o] [-v] [--no-headers]");
+ pw.println(" Print information about installed APEXs on device.");
+ pw.println(" -o: also uses the old digest scheme (SHA256) to compute "
+ + "APEX hashes. WARNING: This can be a very slow and CPU-intensive "
+ + "computation.");
+ pw.println(" -v: lists more verbose information about each APEX.");
+ pw.println(" --no-headers: does not print the header if specified");
pw.println("");
- pw.println(" get module_info [-v]");
- pw.println(" Print information about installed modules on device.");
- pw.println(" -v: lists more verbose information about each module.");
+ pw.println(" get module_info [-o] [-v] [--no-headers]");
+ pw.println(" Print information about installed modules on device.");
+ pw.println(" -o: also uses the old digest scheme (SHA256) to compute "
+ + "module hashes. WARNING: This can be a very slow and "
+ + "CPU-intensive computation.");
+ pw.println(" -v: lists more verbose information about each module.");
+ pw.println(" --no-headers: does not print the header if specified");
pw.println("");
- pw.println(" get mba_info [-v] [-l]");
- pw.println(" Print information about installed mobile bundle apps "
+ pw.println(" get mba_info [-o] [-v] [-l] [--no-headers]");
+ pw.println(" Print information about installed mobile bundle apps "
+ "(MBAs on device).");
- pw.println(" -v: lists more verbose information about each app.");
- pw.println(" -l: lists shared library info. This will only be "
- + "listed with -v");
+ pw.println(" -o: also uses the old digest scheme (SHA256) to compute "
+ + "MBA hashes. WARNING: This can be a very slow and CPU-intensive "
+ + "computation.");
+ pw.println(" -v: lists more verbose information about each app.");
+ pw.println(" -l: lists shared library info. (This option only works "
+ + "when -v option is also specified)");
+ pw.println(" --no-headers: does not print the header if specified");
pw.println("");
}
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index 5d54b6c1c81f..fc26f0989f45 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -17,9 +17,6 @@
package com.android.server;
import static android.Manifest.permission.CONNECTIVITY_INTERNAL;
-import static android.Manifest.permission.NETWORK_SETTINGS;
-import static android.Manifest.permission.OBSERVE_NETWORK_POLICY;
-import static android.Manifest.permission.SHUTDOWN;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_POWERSAVE;
@@ -63,6 +60,7 @@ import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.INetworkManagementService;
+import android.os.PermissionEnforcer;
import android.os.Process;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
@@ -230,6 +228,7 @@ public class NetworkManagementService extends INetworkManagementService.Stub {
*/
private NetworkManagementService(
Context context, Dependencies deps) {
+ super(PermissionEnforcer.fromContext(context));
mContext = context;
mDeps = deps;
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 6b6351f9b909..64358696b07f 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -672,6 +672,7 @@ class StorageManagerService extends IStorageManager.Stub
private static final int H_COMPLETE_UNLOCK_USER = 14;
private static final int H_VOLUME_STATE_CHANGED = 15;
private static final int H_CLOUD_MEDIA_PROVIDER_CHANGED = 16;
+ private static final int H_SECURE_KEYGUARD_STATE_CHANGED = 17;
class StorageManagerServiceHandler extends Handler {
public StorageManagerServiceHandler(Looper looper) {
@@ -819,6 +820,14 @@ class StorageManagerService extends IStorageManager.Stub
}
break;
}
+ case H_SECURE_KEYGUARD_STATE_CHANGED: {
+ try {
+ mVold.onSecureKeyguardStateChanged((boolean) msg.obj);
+ } catch (Exception e) {
+ Slog.wtf(TAG, e);
+ }
+ break;
+ }
}
}
}
@@ -836,7 +845,15 @@ class StorageManagerService extends IStorageManager.Stub
if (Intent.ACTION_USER_ADDED.equals(action)) {
final UserManager um = mContext.getSystemService(UserManager.class);
final int userSerialNumber = um.getUserSerialNumber(userId);
- mVold.onUserAdded(userId, userSerialNumber);
+ final UserInfo userInfo = um.getUserInfo(userId);
+ if (userInfo.isCloneProfile()) {
+ // Only clone profiles share storage with their parent
+ mVold.onUserAdded(userId, userSerialNumber,
+ userInfo.profileGroupId /* sharesStorageWithUserId */);
+ } else {
+ mVold.onUserAdded(userId, userSerialNumber,
+ -1 /* shareStorageWithUserId */);
+ }
} else if (Intent.ACTION_USER_REMOVED.equals(action)) {
synchronized (mLock) {
final int size = mVolumes.size();
@@ -1050,7 +1067,11 @@ class StorageManagerService extends IStorageManager.Stub
// Tell vold about all existing and started users
for (UserInfo user : users) {
- mVold.onUserAdded(user.id, user.serialNumber);
+ if (user.isCloneProfile()) {
+ mVold.onUserAdded(user.id, user.serialNumber, user.profileGroupId);
+ } else {
+ mVold.onUserAdded(user.id, user.serialNumber, -1);
+ }
}
for (int userId : systemUnlockedUsers) {
mVold.onUserStarted(userId);
@@ -1242,12 +1263,12 @@ class StorageManagerService extends IStorageManager.Stub
public void onKeyguardStateChanged(boolean isShowing) {
// Push down current secure keyguard status so that we ignore malicious
// USB devices while locked.
- mSecureKeyguardShowing = isShowing
+ boolean isSecureKeyguardShowing = isShowing
&& mContext.getSystemService(KeyguardManager.class).isDeviceSecure(mCurrentUserId);
- try {
- mVold.onSecureKeyguardStateChanged(mSecureKeyguardShowing);
- } catch (Exception e) {
- Slog.wtf(TAG, e);
+ if (mSecureKeyguardShowing != isSecureKeyguardShowing) {
+ mSecureKeyguardShowing = isSecureKeyguardShowing;
+ mHandler.obtainMessage(H_SECURE_KEYGUARD_STATE_CHANGED, mSecureKeyguardShowing)
+ .sendToTarget();
}
}
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index bd90d85e3fd0..32afccadf455 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -1041,7 +1041,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
String callingFeatureId, IPhoneStateListener callback,
int[] events, boolean notifyNow) {
Set<Integer> eventList = Arrays.stream(events).boxed().collect(Collectors.toSet());
- listen(renounceFineLocationAccess, renounceFineLocationAccess, callingPackage,
+ listen(renounceFineLocationAccess, renounceCoarseLocationAccess, callingPackage,
callingFeatureId, callback, eventList, notifyNow, subId);
}
@@ -1606,7 +1606,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
if (DBG) {
log("notifyServiceStateForSubscriber: callback.onSSC r=" + r
+ " subId=" + subId + " phoneId=" + phoneId
- + " state=" + state);
+ + " state=" + stateToSend);
}
r.callback.onServiceStateChanged(stateToSend);
} catch (RemoteException ex) {
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 4539e9eab424..35b3db8a6332 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -132,6 +132,7 @@ import android.app.compat.CompatChanges;
import android.app.usage.UsageEvents;
import android.appwidget.AppWidgetManagerInternal;
import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
import android.compat.annotation.EnabledSince;
import android.compat.annotation.Overridable;
import android.content.ComponentName;
@@ -150,6 +151,7 @@ import android.content.pm.ServiceInfo;
import android.content.pm.ServiceInfo.ForegroundServiceType;
import android.os.Binder;
import android.os.Build;
+import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import android.os.DeadObjectException;
import android.os.Handler;
@@ -390,6 +392,14 @@ public final class ActiveServices {
@EnabledSince(targetSdkVersion = android.os.Build.VERSION_CODES.S)
static final long FGS_START_EXCEPTION_CHANGE_ID = 174041399L;
+ /**
+ * If enabled, the FGS type check against the manifest FSG type will be enabled for
+ * instant apps too. Before U, this check was only done for non-instant apps.
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = VERSION_CODES.TIRAMISU)
+ static final long FGS_TYPE_CHECK_FOR_INSTANT_APPS = 261055255L;
+
final Runnable mLastAnrDumpClearer = new Runnable() {
@Override public void run() {
synchronized (mAm) {
@@ -1818,39 +1828,44 @@ public final class ActiveServices {
android.Manifest.permission.FOREGROUND_SERVICE,
r.app.getPid(), r.appInfo.uid, "startForeground");
}
+ }
+ final int manifestType = r.serviceInfo.getForegroundServiceType();
+ // If passed in foreground service type is FOREGROUND_SERVICE_TYPE_MANIFEST,
+ // consider it is the same as manifest foreground service type.
+ if (foregroundServiceType == FOREGROUND_SERVICE_TYPE_MANIFEST) {
+ foregroundServiceType = manifestType;
+ }
- // TODO(short-service): This part really should be above the if block,
- // so we'll apply the same check for instant apps too.
- int manifestType = r.serviceInfo.getForegroundServiceType();
- // If passed in foreground service type is FOREGROUND_SERVICE_TYPE_MANIFEST,
- // consider it is the same as manifest foreground service type.
- if (foregroundServiceType == FOREGROUND_SERVICE_TYPE_MANIFEST) {
- foregroundServiceType = manifestType;
- }
-
- // Check the passed in foreground service type flags is a subset of manifest
- // foreground service type flags.
- final String prop = "debug.skip_fgs_manifest_type_check";
- if (((foregroundServiceType & manifestType) != foregroundServiceType)
- // When building a test app on Studio, the SDK may not have all the
- // FGS types yet. This debug flag will allow using FGS types that are
- // not set in the manifest.
- && !SystemProperties.getBoolean(prop, false)) {
- throw new IllegalArgumentException("foregroundServiceType "
+ // Check the passed in foreground service type flags is a subset of manifest
+ // foreground service type flags.
+ final String prop = "debug.skip_fgs_manifest_type_check";
+ if (((foregroundServiceType & manifestType) != foregroundServiceType)
+ // When building a test app on Studio, the SDK may not have all the
+ // FGS types yet. This debug flag will allow using FGS types that are
+ // not set in the manifest.
+ && !SystemProperties.getBoolean(prop, false)) {
+ final String message = "foregroundServiceType "
+ String.format("0x%08X", foregroundServiceType)
+ " is not a subset of foregroundServiceType attribute "
- + String.format("0x%08X", manifestType)
- + " in service element of manifest file");
- }
- if ((foregroundServiceType & FOREGROUND_SERVICE_TYPE_SHORT_SERVICE) != 0
- && foregroundServiceType != FOREGROUND_SERVICE_TYPE_SHORT_SERVICE) {
- Slog.w(TAG_SERVICE, "startForeground(): FOREGROUND_SERVICE_TYPE_SHORT_SERVICE"
- + " is combined with other types. SHORT_SERVICE will be ignored.");
- // In this case, the service will be handled as a non-short, regular FGS
- // anyway, so we just remove the SHORT_SERVICE type.
- foregroundServiceType &= ~FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
+ + String.format("0x%08X", manifestType)
+ + " in service element of manifest file";
+ if (!r.appInfo.isInstantApp()
+ || CompatChanges.isChangeEnabled(FGS_TYPE_CHECK_FOR_INSTANT_APPS,
+ r.appInfo.uid)) {
+ throw new IllegalArgumentException(message);
+ } else {
+ Slog.w(TAG, message + "\n"
+ + "This will be an exception once the target SDK level is UDC");
}
}
+ if ((foregroundServiceType & FOREGROUND_SERVICE_TYPE_SHORT_SERVICE) != 0
+ && foregroundServiceType != FOREGROUND_SERVICE_TYPE_SHORT_SERVICE) {
+ Slog.w(TAG_SERVICE, "startForeground(): FOREGROUND_SERVICE_TYPE_SHORT_SERVICE"
+ + " is combined with other types. SHORT_SERVICE will be ignored.");
+ // In this case, the service will be handled as a non-short, regular FGS
+ // anyway, so we just remove the SHORT_SERVICE type.
+ foregroundServiceType &= ~FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
+ }
boolean alreadyStartedOp = false;
boolean stopProcStatsOp = false;
@@ -2966,6 +2981,9 @@ public final class ActiveServices {
void onShortFgsTimeout(ServiceRecord sr) {
synchronized (mAm) {
if (!sr.shouldTriggerShortFgsTimeout()) {
+ if (DEBUG_SHORT_SERVICE) {
+ Slog.d(TAG_SERVICE, "[STALE] Short FGS timed out: " + sr);
+ }
return;
}
Slog.e(TAG_SERVICE, "Short FGS timed out: " + sr);
@@ -2981,18 +2999,34 @@ public final class ActiveServices {
}
}
+ boolean shouldServiceTimeOutLocked(ComponentName className, IBinder token) {
+ final int userId = UserHandle.getCallingUserId();
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ ServiceRecord sr = findServiceLocked(className, token, userId);
+ if (sr == null) {
+ return false;
+ }
+ return sr.shouldTriggerShortFgsTimeout();
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
void onShortFgsAnrTimeout(ServiceRecord sr) {
final String reason = "A foreground service of FOREGROUND_SERVICE_TYPE_SHORT_SERVICE"
+ " did not stop within a timeout: " + sr.getComponentName();
final TimeoutRecord tr = TimeoutRecord.forShortFgsTimeout(reason);
- // TODO(short-service): TODO Add SHORT_FGS_TIMEOUT to AnrLatencyTracker
tr.mLatencyTracker.waitingOnAMSLockStarted();
synchronized (mAm) {
tr.mLatencyTracker.waitingOnAMSLockEnded();
if (!sr.shouldTriggerShortFgsAnr()) {
+ if (DEBUG_SHORT_SERVICE) {
+ Slog.d(TAG_SERVICE, "[STALE] Short FGS ANR'ed: " + sr);
+ }
return;
}
@@ -3809,6 +3843,11 @@ public final class ActiveServices {
throw new SecurityException("BIND_EXTERNAL_SERVICE failed, "
+ className + " is not an isolatedProcess");
}
+ if (AppGlobals.getPackageManager().getPackageUid(callingPackage,
+ 0, userId) != callingUid) {
+ throw new SecurityException("BIND_EXTERNAL_SERVICE failed, "
+ + "calling package not owned by calling UID ");
+ }
// Run the service under the calling package's application.
ApplicationInfo aInfo = AppGlobals.getPackageManager().getApplicationInfo(
callingPackage, ActivityManagerService.STOCK_PM_FLAGS, userId);
@@ -5392,6 +5431,13 @@ public final class ActiveServices {
// This is a call from a service start... take care of
// book-keeping.
r.callStart = true;
+
+ // Set the result to startCommandResult.
+ // START_TASK_REMOVED_COMPLETE is _not_ a result from onStartCommand(), so
+ // let's ignore.
+ if (res != Service.START_TASK_REMOVED_COMPLETE) {
+ r.startCommandResult = res;
+ }
switch (res) {
case Service.START_STICKY_COMPATIBILITY:
case Service.START_STICKY: {
@@ -7673,4 +7719,35 @@ public final class ActiveServices {
Slog.e(TAG, "stopForegroundServiceDelegateLocked delegate does not exist");
}
}
+
+ private static void getClientPackages(ServiceRecord sr, ArraySet<String> output) {
+ var connections = sr.getConnections();
+ for (int conni = connections.size() - 1; conni >= 0; conni--) {
+ var connl = connections.valueAt(conni);
+ for (int i = 0, size = connl.size(); i < size; i++) {
+ var conn = connl.get(i);
+ if (conn.binding.client != null) {
+ output.add(conn.binding.client.info.packageName);
+ }
+ }
+ }
+ }
+
+ /**
+ * Return all client package names of a service.
+ */
+ ArraySet<String> getClientPackagesLocked(@NonNull String servicePackageName) {
+ var results = new ArraySet<String>();
+ int[] users = mAm.mUserController.getUsers();
+ for (int ui = 0; ui < users.length; ui++) {
+ ArrayMap<ComponentName, ServiceRecord> alls = getServicesLocked(users[ui]);
+ for (int i = 0, size = alls.size(); i < size; i++) {
+ ServiceRecord sr = alls.valueAt(i);
+ if (sr.name.getPackageName().equals(servicePackageName)) {
+ getClientPackages(sr, results);
+ }
+ }
+ }
+ return results;
+ }
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index c51e14fdc8a2..fd24300de29d 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -328,6 +328,7 @@ import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.EventLog;
import android.util.FeatureFlagUtils;
+import android.util.IndentingPrintWriter;
import android.util.IntArray;
import android.util.Log;
import android.util.Pair;
@@ -759,6 +760,9 @@ public class ActivityManagerService extends IActivityManager.Stub
final AppErrors mAppErrors;
final PackageWatchdog mPackageWatchdog;
+ @GuardedBy("mDeliveryGroupPolicyIgnoredActions")
+ private final ArraySet<String> mDeliveryGroupPolicyIgnoredActions = new ArraySet();
+
/**
* Uids of apps with current active camera sessions. Access synchronized on
* the IntArray instance itself, and no other locks must be acquired while that
@@ -12983,6 +12987,13 @@ public class ActivityManagerService extends IActivityManager.Stub
}
@Override
+ public boolean shouldServiceTimeOut(ComponentName className, IBinder token) {
+ synchronized (this) {
+ return mServices.shouldServiceTimeOutLocked(className, token);
+ }
+ }
+
+ @Override
public int handleIncomingUser(int callingPid, int callingUid, int userId, boolean allowAll,
boolean requireFull, String name, String callerPackage) {
return mUserController.handleIncomingUser(callingPid, callingUid, userId, allowAll,
@@ -18184,6 +18195,13 @@ public class ActivityManagerService extends IActivityManager.Stub
mServices.stopForegroundServiceDelegateLocked(connection);
}
}
+
+ @Override
+ public ArraySet<String> getClientPackages(String servicePackageName) {
+ synchronized (ActivityManagerService.this) {
+ return mServices.getClientPackagesLocked(servicePackageName);
+ }
+ }
}
long inputDispatchingTimedOut(int pid, final boolean aboveSystem, TimeoutRecord timeoutRecord) {
@@ -18315,14 +18333,47 @@ public class ActivityManagerService extends IActivityManager.Stub
}
}
- public void waitForBroadcastBarrier(@Nullable PrintWriter pw) {
+ public void waitForBroadcastBarrier(@Nullable PrintWriter pw, boolean flushBroadcastLoopers) {
enforceCallingPermission(permission.DUMP, "waitForBroadcastBarrier()");
- BroadcastLoopers.waitForIdle(pw);
+ if (flushBroadcastLoopers) {
+ BroadcastLoopers.waitForBarrier(pw);
+ }
for (BroadcastQueue queue : mBroadcastQueues) {
queue.waitForBarrier(pw);
}
}
+ void setIgnoreDeliveryGroupPolicy(@NonNull String broadcastAction) {
+ Objects.requireNonNull(broadcastAction);
+ enforceCallingPermission(permission.DUMP, "waitForBroadcastBarrier()");
+ synchronized (mDeliveryGroupPolicyIgnoredActions) {
+ mDeliveryGroupPolicyIgnoredActions.add(broadcastAction);
+ }
+ }
+
+ void clearIgnoreDeliveryGroupPolicy(@NonNull String broadcastAction) {
+ Objects.requireNonNull(broadcastAction);
+ enforceCallingPermission(permission.DUMP, "waitForBroadcastBarrier()");
+ synchronized (mDeliveryGroupPolicyIgnoredActions) {
+ mDeliveryGroupPolicyIgnoredActions.remove(broadcastAction);
+ }
+ }
+
+ boolean shouldIgnoreDeliveryGroupPolicy(@Nullable String broadcastAction) {
+ if (broadcastAction == null) {
+ return false;
+ }
+ synchronized (mDeliveryGroupPolicyIgnoredActions) {
+ return mDeliveryGroupPolicyIgnoredActions.contains(broadcastAction);
+ }
+ }
+
+ void dumpDeliveryGroupPolicyIgnoredActions(IndentingPrintWriter ipw) {
+ synchronized (mDeliveryGroupPolicyIgnoredActions) {
+ ipw.println(mDeliveryGroupPolicyIgnoredActions);
+ }
+ }
+
@Override
@ReasonCode
public int getBackgroundRestrictionExemptionReason(int uid) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 7946cb74f117..94c15babb1e5 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -345,6 +345,10 @@ final class ActivityManagerShellCommand extends ShellCommand {
return runWaitForBroadcastIdle(pw);
case "wait-for-broadcast-barrier":
return runWaitForBroadcastBarrier(pw);
+ case "set-ignore-delivery-group-policy":
+ return runSetIgnoreDeliveryGroupPolicy(pw);
+ case "clear-ignore-delivery-group-policy":
+ return runClearIgnoreDeliveryGroupPolicy(pw);
case "compat":
return runCompat(pw);
case "refresh-settings-cache":
@@ -906,7 +910,8 @@ final class ActivityManagerShellCommand extends ShellCommand {
}
}
- // TODO(b/239982558): might need to support --displayId as well
+ // NOTE: current profiles can only be started on default display (even on automotive builds with
+ // passenger displays), so there's no need to pass a display-id
private int runProfile(PrintWriter pw) throws RemoteException {
final PrintWriter err = getErrPrintWriter();
String profileFile = null;
@@ -2005,12 +2010,6 @@ final class ActivityManagerShellCommand extends ShellCommand {
}
int runSwitchUser(PrintWriter pw) throws RemoteException {
- UserManager userManager = mInternal.mContext.getSystemService(UserManager.class);
- final int userSwitchable = userManager.getUserSwitchability();
- if (userSwitchable != UserManager.SWITCHABILITY_STATUS_OK) {
- getErrPrintWriter().println("Error: " + userSwitchable);
- return -1;
- }
boolean wait = false;
String opt;
while ((opt = getNextOption()) != null) {
@@ -2023,6 +2022,14 @@ final class ActivityManagerShellCommand extends ShellCommand {
}
int userId = Integer.parseInt(getNextArgRequired());
+
+ UserManager userManager = mInternal.mContext.getSystemService(UserManager.class);
+ final int userSwitchable = userManager.getUserSwitchability(UserHandle.of(userId));
+ if (userSwitchable != UserManager.SWITCHABILITY_STATUS_OK) {
+ getErrPrintWriter().println("Error: UserSwitchabilityResult=" + userSwitchable);
+ return -1;
+ }
+
boolean switched;
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "shell_runSwitchUser");
try {
@@ -3130,7 +3137,29 @@ final class ActivityManagerShellCommand extends ShellCommand {
}
int runWaitForBroadcastBarrier(PrintWriter pw) throws RemoteException {
- mInternal.waitForBroadcastBarrier(pw);
+ boolean flushBroadcastLoopers = false;
+ String opt;
+ while ((opt = getNextOption()) != null) {
+ if (opt.equals("--flush-broadcast-loopers")) {
+ flushBroadcastLoopers = true;
+ } else {
+ getErrPrintWriter().println("Error: Unknown option: " + opt);
+ return -1;
+ }
+ }
+ mInternal.waitForBroadcastBarrier(pw, flushBroadcastLoopers);
+ return 0;
+ }
+
+ int runSetIgnoreDeliveryGroupPolicy(PrintWriter pw) throws RemoteException {
+ final String broadcastAction = getNextArgRequired();
+ mInternal.setIgnoreDeliveryGroupPolicy(broadcastAction);
+ return 0;
+ }
+
+ int runClearIgnoreDeliveryGroupPolicy(PrintWriter pw) throws RemoteException {
+ final String broadcastAction = getNextArgRequired();
+ mInternal.clearIgnoreDeliveryGroupPolicy(broadcastAction);
return 0;
}
@@ -4018,7 +4047,10 @@ final class ActivityManagerShellCommand extends ShellCommand {
+ "background.");
pw.println(" set-foreground-service-delegate [--user <USER_ID>] <PACKAGE> start|stop");
pw.println(" Start/stop an app's foreground service delegate.");
- pw.println();
+ pw.println(" set-ignore-delivery-group-policy <ACTION>");
+ pw.println(" Start ignoring delivery group policy set for a broadcast action");
+ pw.println(" clear-ignore-delivery-group-policy <ACTION>");
+ pw.println(" Stop ignoring delivery group policy set for a broadcast action");
Intent.printIntentArgsHelp(pw, "");
}
}
diff --git a/services/core/java/com/android/server/am/BroadcastConstants.java b/services/core/java/com/android/server/am/BroadcastConstants.java
index 1eebd0127818..f5d1c106705d 100644
--- a/services/core/java/com/android/server/am/BroadcastConstants.java
+++ b/services/core/java/com/android/server/am/BroadcastConstants.java
@@ -153,12 +153,27 @@ public class BroadcastConstants {
"bcast_extra_running_urgent_process_queues";
private static final int DEFAULT_EXTRA_RUNNING_URGENT_PROCESS_QUEUES = 1;
+ /**
+ * For {@link BroadcastQueueModernImpl}: Maximum number of consecutive urgent
+ * broadcast dispatches allowed before letting broadcasts in lower priority queue
+ * to be scheduled in order to avoid starvation.
+ */
public int MAX_CONSECUTIVE_URGENT_DISPATCHES = DEFAULT_MAX_CONSECUTIVE_URGENT_DISPATCHES;
private static final String KEY_MAX_CONSECUTIVE_URGENT_DISPATCHES =
"bcast_max_consecutive_urgent_dispatches";
private static final int DEFAULT_MAX_CONSECUTIVE_URGENT_DISPATCHES = 3;
/**
+ * For {@link BroadcastQueueModernImpl}: Maximum number of consecutive normal
+ * broadcast dispatches allowed before letting broadcasts in lower priority queue
+ * to be scheduled in order to avoid starvation.
+ */
+ public int MAX_CONSECUTIVE_NORMAL_DISPATCHES = DEFAULT_MAX_CONSECUTIVE_NORMAL_DISPATCHES;
+ private static final String KEY_MAX_CONSECUTIVE_NORMAL_DISPATCHES =
+ "bcast_max_consecutive_normal_dispatches";
+ private static final int DEFAULT_MAX_CONSECUTIVE_NORMAL_DISPATCHES = 10;
+
+ /**
* For {@link BroadcastQueueModernImpl}: Maximum number of active broadcasts
* to dispatch to a "running" process queue before we retire them back to
* being "runnable" to give other processes a chance to run.
@@ -341,6 +356,9 @@ public class BroadcastConstants {
MAX_CONSECUTIVE_URGENT_DISPATCHES = getDeviceConfigInt(
KEY_MAX_CONSECUTIVE_URGENT_DISPATCHES,
DEFAULT_MAX_CONSECUTIVE_URGENT_DISPATCHES);
+ MAX_CONSECUTIVE_NORMAL_DISPATCHES = getDeviceConfigInt(
+ KEY_MAX_CONSECUTIVE_NORMAL_DISPATCHES,
+ DEFAULT_MAX_CONSECUTIVE_NORMAL_DISPATCHES);
MAX_RUNNING_ACTIVE_BROADCASTS = getDeviceConfigInt(KEY_MAX_RUNNING_ACTIVE_BROADCASTS,
DEFAULT_MAX_RUNNING_ACTIVE_BROADCASTS);
MAX_PENDING_BROADCASTS = getDeviceConfigInt(KEY_MAX_PENDING_BROADCASTS,
@@ -396,6 +414,10 @@ public class BroadcastConstants {
TimeUtils.formatDuration(DELAY_URGENT_MILLIS)).println();
pw.print(KEY_MAX_HISTORY_COMPLETE_SIZE, MAX_HISTORY_COMPLETE_SIZE).println();
pw.print(KEY_MAX_HISTORY_SUMMARY_SIZE, MAX_HISTORY_SUMMARY_SIZE).println();
+ pw.print(KEY_MAX_CONSECUTIVE_URGENT_DISPATCHES,
+ MAX_CONSECUTIVE_URGENT_DISPATCHES).println();
+ pw.print(KEY_MAX_CONSECUTIVE_NORMAL_DISPATCHES,
+ MAX_CONSECUTIVE_NORMAL_DISPATCHES).println();
pw.decreaseIndent();
pw.println();
}
diff --git a/services/core/java/com/android/server/am/BroadcastLoopers.java b/services/core/java/com/android/server/am/BroadcastLoopers.java
index b828720c9162..a5535cb13165 100644
--- a/services/core/java/com/android/server/am/BroadcastLoopers.java
+++ b/services/core/java/com/android/server/am/BroadcastLoopers.java
@@ -18,6 +18,7 @@ package com.android.server.am;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.MessageQueue;
@@ -30,6 +31,7 @@ import com.android.internal.annotations.GuardedBy;
import java.io.PrintWriter;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
+import java.util.function.BiConsumer;
/**
* Collection of {@link Looper} that are known to be used for broadcast dispatch
@@ -73,19 +75,44 @@ public class BroadcastLoopers {
* still in the future are ignored for the purposes of the idle test.
*/
public static void waitForIdle(@Nullable PrintWriter pw) {
+ waitForCondition(pw, (looper, latch) -> {
+ final MessageQueue queue = looper.getQueue();
+ queue.addIdleHandler(() -> {
+ latch.countDown();
+ return false;
+ });
+ });
+ }
+
+ /**
+ * Wait for all registered {@link Looper} instances to handle currently waiting messages.
+ * Note that {@link Message#when} still in the future are ignored for the purposes
+ * of the idle test.
+ */
+ public static void waitForBarrier(@Nullable PrintWriter pw) {
+ waitForCondition(pw, (looper, latch) -> {
+ (new Handler(looper)).post(() -> {
+ latch.countDown();
+ });
+ });
+ }
+
+ /**
+ * Wait for all registered {@link Looper} instances to meet a certain condition.
+ */
+ private static void waitForCondition(@Nullable PrintWriter pw,
+ @NonNull BiConsumer<Looper, CountDownLatch> condition) {
final CountDownLatch latch;
synchronized (sLoopers) {
final int N = sLoopers.size();
latch = new CountDownLatch(N);
for (int i = 0; i < N; i++) {
- final MessageQueue queue = sLoopers.valueAt(i).getQueue();
+ final Looper looper = sLoopers.valueAt(i);
+ final MessageQueue queue = looper.getQueue();
if (queue.isIdle()) {
latch.countDown();
} else {
- queue.addIdleHandler(() -> {
- latch.countDown();
- return false;
- });
+ condition.accept(looper, latch);
}
}
}
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index 66d7fc92340a..15d2fa334dcf 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -153,6 +153,12 @@ class BroadcastProcessQueue {
private int mActiveCountConsecutiveUrgent;
/**
+ * Number of consecutive normal broadcasts that have been dispatched
+ * since the last offload dispatch.
+ */
+ private int mActiveCountConsecutiveNormal;
+
+ /**
* Count of pending broadcasts of these various flavors.
*/
private int mCountForeground;
@@ -164,6 +170,8 @@ class BroadcastProcessQueue {
private int mCountInstrumented;
private int mCountManifest;
+ private boolean mPrioritizeEarliest;
+
private @UptimeMillisLong long mRunnableAt = Long.MAX_VALUE;
private @Reason int mRunnableAtReason = REASON_EMPTY;
private boolean mRunnableAtInvalidated;
@@ -551,48 +559,75 @@ class BroadcastProcessQueue {
* Will thrown an exception if there are no pending broadcasts; relies on
* {@link #isEmpty()} being false.
*/
- SomeArgs removeNextBroadcast() {
+ private @Nullable SomeArgs removeNextBroadcast() {
final ArrayDeque<SomeArgs> queue = queueForNextBroadcast();
if (queue == mPendingUrgent) {
mActiveCountConsecutiveUrgent++;
- } else {
+ } else if (queue == mPending) {
+ mActiveCountConsecutiveUrgent = 0;
+ mActiveCountConsecutiveNormal++;
+ } else if (queue == mPendingOffload) {
mActiveCountConsecutiveUrgent = 0;
+ mActiveCountConsecutiveNormal = 0;
}
- return queue.removeFirst();
+ return !isQueueEmpty(queue) ? queue.removeFirst() : null;
}
@Nullable ArrayDeque<SomeArgs> queueForNextBroadcast() {
- ArrayDeque<SomeArgs> nextUrgent = mPendingUrgent.isEmpty() ? null : mPendingUrgent;
- ArrayDeque<SomeArgs> nextNormal = null;
- if (!mPending.isEmpty()) {
- nextNormal = mPending;
- } else if (!mPendingOffload.isEmpty()) {
- nextNormal = mPendingOffload;
- }
- // nothing urgent pending, no further decisionmaking
- if (nextUrgent == null) {
- return nextNormal;
+ final ArrayDeque<SomeArgs> nextNormal = queueForNextBroadcast(
+ mPending, mPendingOffload,
+ mActiveCountConsecutiveNormal, constants.MAX_CONSECUTIVE_NORMAL_DISPATCHES);
+ final ArrayDeque<SomeArgs> nextBroadcastQueue = queueForNextBroadcast(
+ mPendingUrgent, nextNormal,
+ mActiveCountConsecutiveUrgent, constants.MAX_CONSECUTIVE_URGENT_DISPATCHES);
+ return nextBroadcastQueue;
+ }
+
+ private @Nullable ArrayDeque<SomeArgs> queueForNextBroadcast(
+ @Nullable ArrayDeque<SomeArgs> highPriorityQueue,
+ @Nullable ArrayDeque<SomeArgs> lowPriorityQueue,
+ int consecutiveHighPriorityCount,
+ int maxHighPriorityDispatchLimit) {
+ // nothing high priority pending, no further decisionmaking
+ if (isQueueEmpty(highPriorityQueue)) {
+ return lowPriorityQueue;
}
- // nothing but urgent pending, also no further decisionmaking
- if (nextNormal == null) {
- return nextUrgent;
+ // nothing but high priority pending, also no further decisionmaking
+ if (isQueueEmpty(lowPriorityQueue)) {
+ return highPriorityQueue;
}
- // Starvation mitigation: although we prioritize urgent broadcasts by default,
- // we allow non-urgent deliveries to make steady progress even if urgent
- // broadcasts are arriving faster than they can be dispatched.
+ // Starvation mitigation: although we prioritize high priority queues by default,
+ // we allow low priority queues to make steady progress even if broadcasts in
+ // high priority queue are arriving faster than they can be dispatched.
//
- // We do not try to defer to the next non-urgent broadcast if that broadcast
+ // We do not try to defer to the next broadcast in low priority queues if that broadcast
// is ordered and still blocked on delivery to other recipients.
- final SomeArgs nextNormalArgs = nextNormal.peekFirst();
- final BroadcastRecord rNormal = (BroadcastRecord) nextNormalArgs.arg1;
- final int nextNormalIndex = nextNormalArgs.argi1;
- final BroadcastRecord rUrgent = (BroadcastRecord) nextUrgent.peekFirst().arg1;
- final boolean canTakeNormal =
- mActiveCountConsecutiveUrgent >= constants.MAX_CONSECUTIVE_URGENT_DISPATCHES
- && rNormal.enqueueTime <= rUrgent.enqueueTime
- && !blockedOnOrderedDispatch(rNormal, nextNormalIndex);
- return canTakeNormal ? nextNormal : nextUrgent;
+ final SomeArgs nextLPArgs = lowPriorityQueue.peekFirst();
+ final BroadcastRecord nextLPRecord = (BroadcastRecord) nextLPArgs.arg1;
+ final int nextLPRecordIndex = nextLPArgs.argi1;
+ final BroadcastRecord nextHPRecord = (BroadcastRecord) highPriorityQueue.peekFirst().arg1;
+ final boolean shouldConsiderLPQueue = (mPrioritizeEarliest
+ || consecutiveHighPriorityCount >= maxHighPriorityDispatchLimit);
+ final boolean isLPQueueEligible = shouldConsiderLPQueue
+ && nextLPRecord.enqueueTime <= nextHPRecord.enqueueTime
+ && !blockedOnOrderedDispatch(nextLPRecord, nextLPRecordIndex);
+ return isLPQueueEligible ? lowPriorityQueue : highPriorityQueue;
+ }
+
+ private static boolean isQueueEmpty(@Nullable ArrayDeque<SomeArgs> queue) {
+ return (queue == null || queue.isEmpty());
+ }
+
+ /**
+ * When {@code prioritizeEarliest} is set to {@code true}, then earliest enqueued
+ * broadcasts would be prioritized for dispatching, even if there are urgent broadcasts
+ * waiting. This is typically used in case there are callers waiting for "barrier" to be
+ * reached.
+ */
+ @VisibleForTesting
+ void setPrioritizeEarliest(boolean prioritizeEarliest) {
+ mPrioritizeEarliest = prioritizeEarliest;
}
/**
@@ -600,13 +635,13 @@ class BroadcastProcessQueue {
*/
@Nullable SomeArgs peekNextBroadcast() {
ArrayDeque<SomeArgs> queue = queueForNextBroadcast();
- return (queue != null) ? queue.peekFirst() : null;
+ return !isQueueEmpty(queue) ? queue.peekFirst() : null;
}
@VisibleForTesting
@Nullable BroadcastRecord peekNextBroadcastRecord() {
ArrayDeque<SomeArgs> queue = queueForNextBroadcast();
- return (queue != null) ? (BroadcastRecord) queue.peekFirst().arg1 : null;
+ return !isQueueEmpty(queue) ? (BroadcastRecord) queue.peekFirst().arg1 : null;
}
/**
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index 5d5dbbbed03f..eb5c03bec560 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -91,6 +91,7 @@ import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.function.BooleanSupplier;
+import java.util.function.Consumer;
import java.util.function.Predicate;
/**
@@ -220,10 +221,19 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
private void enqueueFinishReceiver(@NonNull BroadcastProcessQueue queue,
@DeliveryState int deliveryState, @NonNull String reason) {
+ enqueueFinishReceiver(queue, queue.getActive(), queue.getActiveIndex(),
+ deliveryState, reason);
+ }
+
+ private void enqueueFinishReceiver(@NonNull BroadcastProcessQueue queue,
+ @NonNull BroadcastRecord r, int index,
+ @DeliveryState int deliveryState, @NonNull String reason) {
final SomeArgs args = SomeArgs.obtain();
args.arg1 = queue;
args.argi1 = deliveryState;
args.arg2 = reason;
+ args.arg3 = r;
+ args.argi2 = index;
mLocalHandler.sendMessage(Message.obtain(mLocalHandler, MSG_FINISH_RECEIVER, args));
}
@@ -271,8 +281,10 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
final BroadcastProcessQueue queue = (BroadcastProcessQueue) args.arg1;
final int deliveryState = args.argi1;
final String reason = (String) args.arg2;
+ final BroadcastRecord r = (BroadcastRecord) args.arg3;
+ final int index = args.argi2;
args.recycle();
- finishReceiverLocked(queue, deliveryState, reason);
+ finishReceiverLocked(queue, deliveryState, reason, r, index);
}
return true;
}
@@ -636,6 +648,9 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
}
private void applyDeliveryGroupPolicy(@NonNull BroadcastRecord r) {
+ if (mService.shouldIgnoreDeliveryGroupPolicy(r.intent.getAction())) {
+ return;
+ }
final int policy = (r.options != null)
? r.options.getDeliveryGroupPolicy() : BroadcastOptions.DELIVERY_GROUP_POLICY_ALL;
final BroadcastConsumer broadcastConsumer;
@@ -729,10 +744,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
private void scheduleReceiverWarmLocked(@NonNull BroadcastProcessQueue queue) {
checkState(queue.isActive(), "isActive");
- final ProcessRecord app = queue.app;
final BroadcastRecord r = queue.getActive();
final int index = queue.getActiveIndex();
- final Object receiver = r.receivers.get(index);
if (r.terminalCount == 0) {
r.dispatchTime = SystemClock.uptimeMillis();
@@ -740,26 +753,40 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
r.dispatchClockTime = System.currentTimeMillis();
}
- // If someone already finished this broadcast, finish immediately
+ if (maybeSkipReceiver(queue, r, index)) {
+ return;
+ }
+ dispatchReceivers(queue, r, index);
+ }
+
+ /**
+ * Examine a receiver and possibly skip it. The method returns true if the receiver is
+ * skipped (and therefore no more work is required).
+ */
+ private boolean maybeSkipReceiver(BroadcastProcessQueue queue, BroadcastRecord r, int index) {
final int oldDeliveryState = getDeliveryState(r, index);
+ final ProcessRecord app = queue.app;
+ final Object receiver = r.receivers.get(index);
+
+ // If someone already finished this broadcast, finish immediately
if (isDeliveryStateTerminal(oldDeliveryState)) {
enqueueFinishReceiver(queue, oldDeliveryState, "already terminal state");
- return;
+ return true;
}
// Consider additional cases where we'd want to finish immediately
if (app.isInFullBackup()) {
enqueueFinishReceiver(queue, BroadcastRecord.DELIVERY_SKIPPED, "isInFullBackup");
- return;
+ return true;
}
if (mSkipPolicy.shouldSkip(r, receiver)) {
enqueueFinishReceiver(queue, BroadcastRecord.DELIVERY_SKIPPED, "mSkipPolicy");
- return;
+ return true;
}
final Intent receiverIntent = r.getReceiverIntent(receiver);
if (receiverIntent == null) {
enqueueFinishReceiver(queue, BroadcastRecord.DELIVERY_SKIPPED, "getReceiverIntent");
- return;
+ return true;
}
// Ignore registered receivers from a previous PID
@@ -767,12 +794,29 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
&& ((BroadcastFilter) receiver).receiverList.pid != app.getPid()) {
enqueueFinishReceiver(queue, BroadcastRecord.DELIVERY_SKIPPED,
"BroadcastFilter for mismatched PID");
- return;
+ return true;
}
+ // The receiver was not handled in this method.
+ return false;
+ }
+
+ /**
+ * Return true if this receiver should be assumed to have been delivered.
+ */
+ private boolean isAssumedDelivered(BroadcastRecord r, int index) {
+ return (r.receivers.get(index) instanceof BroadcastFilter) && !r.ordered;
+ }
+
+ /**
+ * A receiver is about to be dispatched. Start ANR timers, if necessary.
+ */
+ private void dispatchReceivers(BroadcastProcessQueue queue, BroadcastRecord r, int index) {
+ final ProcessRecord app = queue.app;
+ final Object receiver = r.receivers.get(index);
// Skip ANR tracking early during boot, when requested, or when we
// immediately assume delivery success
- final boolean assumeDelivered = (receiver instanceof BroadcastFilter) && !r.ordered;
+ final boolean assumeDelivered = isAssumedDelivered(r, index);
if (mService.mProcessesReady && !r.timeoutExempt && !assumeDelivered) {
queue.lastCpuDelayTime = queue.app.getCpuDelayTime();
@@ -805,6 +849,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
setDeliveryState(queue, app, r, index, receiver, BroadcastRecord.DELIVERY_SCHEDULED,
"scheduleReceiverWarmLocked");
+ final Intent receiverIntent = r.getReceiverIntent(receiver);
final IApplicationThread thread = app.getOnewayThread();
if (thread != null) {
try {
@@ -920,6 +965,19 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
return finishReceiverLocked(queue, BroadcastRecord.DELIVERY_DELIVERED, "remote app");
}
+ /**
+ * Return true if there are more broadcasts in the queue and the queue is runnable.
+ */
+ private boolean shouldContinueScheduling(@NonNull BroadcastProcessQueue queue) {
+ // If we've made reasonable progress, periodically retire ourselves to
+ // avoid starvation of other processes and stack overflow when a
+ // broadcast is immediately finished without waiting
+ final boolean shouldRetire =
+ (queue.getActiveCountSinceIdle() >= mConstants.MAX_RUNNING_ACTIVE_BROADCASTS);
+
+ return queue.isRunnable() && queue.isProcessWarm() && !shouldRetire;
+ }
+
private boolean finishReceiverLocked(@NonNull BroadcastProcessQueue queue,
@DeliveryState int deliveryState, @NonNull String reason) {
if (!queue.isActive()) {
@@ -927,10 +985,21 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
return false;
}
- final int cookie = traceBegin("finishReceiver");
- final ProcessRecord app = queue.app;
final BroadcastRecord r = queue.getActive();
final int index = queue.getActiveIndex();
+ return finishReceiverLocked(queue, deliveryState, reason, r, index);
+ }
+
+ private boolean finishReceiverLocked(@NonNull BroadcastProcessQueue queue,
+ @DeliveryState int deliveryState, @NonNull String reason,
+ BroadcastRecord r, int index) {
+ if (!queue.isActive()) {
+ logw("Ignoring finish; no active broadcast for " + queue);
+ return false;
+ }
+
+ final int cookie = traceBegin("finishReceiver");
+ final ProcessRecord app = queue.app;
final Object receiver = r.receivers.get(index);
setDeliveryState(queue, app, r, index, receiver, deliveryState, reason);
@@ -945,18 +1014,11 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
mLocalHandler.removeMessages(MSG_DELIVERY_TIMEOUT_HARD, queue);
}
- // If we've made reasonable progress, periodically retire ourselves to
- // avoid starvation of other processes and stack overflow when a
- // broadcast is immediately finished without waiting
- final boolean shouldRetire =
- (queue.getActiveCountSinceIdle() >= mConstants.MAX_RUNNING_ACTIVE_BROADCASTS);
-
- final boolean res;
- if (queue.isRunnable() && queue.isProcessWarm() && !shouldRetire) {
+ final boolean res = shouldContinueScheduling(queue);
+ if (res) {
// We're on a roll; move onto the next broadcast for this process
queue.makeActiveNextPending();
scheduleReceiverWarmLocked(queue);
- res = true;
} else {
// We've drained running broadcasts; maybe move back to runnable
queue.makeActiveIdle();
@@ -970,7 +1032,6 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
// Tell other OS components that app is not actively running, giving
// a chance to update OOM adjustment
notifyStoppedRunning(queue);
- res = false;
}
traceEnd(cookie);
return res;
@@ -1167,6 +1228,17 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
return didSomething;
}
+ private void forEachQueue(@NonNull Consumer<BroadcastProcessQueue> consumer) {
+ for (int i = 0; i < mProcessQueues.size(); ++i) {
+ BroadcastProcessQueue leaf = mProcessQueues.valueAt(i);
+ while (leaf != null) {
+ consumer.accept(leaf);
+ updateRunnableList(leaf);
+ leaf = leaf.processNameNext;
+ }
+ }
+ }
+
@Override
public void start(@NonNull ContentResolver resolver) {
mFgConstants.startObserving(mHandler, resolver);
@@ -1225,12 +1297,19 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
final CountDownLatch latch = new CountDownLatch(1);
synchronized (mService) {
mWaitingFor.add(Pair.create(condition, latch));
+ forEachQueue(q -> q.setPrioritizeEarliest(true));
}
enqueueUpdateRunningList();
try {
latch.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
+ } finally {
+ synchronized (mService) {
+ if (mWaitingFor.isEmpty()) {
+ forEachQueue(q -> q.setPrioritizeEarliest(false));
+ }
+ }
}
}
@@ -1442,10 +1521,10 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
private void notifyFinishBroadcast(@NonNull BroadcastRecord r) {
mService.notifyBroadcastFinishedLocked(r);
- mHistory.addBroadcastToHistoryLocked(r);
-
r.finishTime = SystemClock.uptimeMillis();
r.nextReceiver = r.receivers.size();
+ mHistory.addBroadcastToHistoryLocked(r);
+
BroadcastQueueImpl.logBootCompletedBroadcastCompletionLatencyIfPossible(r);
if (r.intent.getComponent() == null && r.intent.getPackage() == null
@@ -1609,6 +1688,12 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
ipw.decreaseIndent();
ipw.println();
+ ipw.println(" Broadcasts with ignored delivery group policies:");
+ ipw.increaseIndent();
+ mService.dumpDeliveryGroupPolicyIgnoredActions(ipw);
+ ipw.decreaseIndent();
+ ipw.println();
+
if (dumpConstants) {
mConstants.dump(ipw);
}
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 937bbc9cced6..4d559b095c53 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -4205,7 +4205,7 @@ public final class ProcessList {
total - mLruProcessServiceStart);
writeProcessOomListToProto(proto,
ActivityManagerServiceDumpProcessesProto.LruProcesses.LIST, mService,
- mLruProcesses, false, dumpPackage);
+ mLruProcesses, true, dumpPackage);
proto.end(lruToken);
}
diff --git a/services/core/java/com/android/server/am/SameProcessApplicationThread.java b/services/core/java/com/android/server/am/SameProcessApplicationThread.java
index a3c011188539..62fd6e9d551b 100644
--- a/services/core/java/com/android/server/am/SameProcessApplicationThread.java
+++ b/services/core/java/com/android/server/am/SameProcessApplicationThread.java
@@ -18,6 +18,7 @@ package com.android.server.am;
import android.annotation.NonNull;
import android.app.IApplicationThread;
+import android.app.ReceiverInfo;
import android.content.IIntentReceiver;
import android.content.Intent;
import android.content.pm.ActivityInfo;
@@ -26,6 +27,7 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.RemoteException;
+import java.util.List;
import java.util.Objects;
/**
@@ -70,4 +72,20 @@ public class SameProcessApplicationThread extends IApplicationThread.Default {
}
});
}
+
+ @Override
+ public void scheduleReceiverList(List<ReceiverInfo> info) {
+ for (int i = 0; i < info.size(); i++) {
+ ReceiverInfo r = info.get(i);
+ if (r.registered) {
+ scheduleRegisteredReceiver(r.receiver, r.intent,
+ r.resultCode, r.data, r.extras, r.ordered, r.sticky,
+ r.sendingUser, r.processState);
+ } else {
+ scheduleReceiver(r.intent, r.activityInfo, r.compatInfo,
+ r.resultCode, r.data, r.extras, r.sync,
+ r.sendingUser, r.processState);
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 547c20b55a3c..8ac10b85f634 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -128,6 +128,7 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN
boolean delayedStop; // service has been stopped but is in a delayed start?
boolean stopIfKilled; // last onStart() said to stop if service killed?
boolean callStart; // last onStart() has asked to always be called on restart.
+ int startCommandResult; // last result from onStartCommand(), only for dumpsys.
int executeNesting; // number of outstanding operations keeping foreground.
boolean executeFg; // should we be executing in the foreground?
long executingStart; // start time of last execute request.
@@ -364,9 +365,6 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN
* Note, we do _not_ check the "start id" here, because the start id increments if the
* app calls startService() or startForegroundService() on the same service,
* but that will _not_ update the ShortFgsInfo, and will not extend the timeout.
- *
- * TODO(short-service): Make sure, calling startService will not extend or remove the
- * timeout, in CTS.
*/
boolean isCurrent() {
return this.mStartForegroundCount == ServiceRecord.this.mStartForegroundCount;
@@ -481,6 +479,7 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN
proto.write(ServiceRecordProto.Start.DELAYED_STOP, delayedStop);
proto.write(ServiceRecordProto.Start.STOP_IF_KILLED, stopIfKilled);
proto.write(ServiceRecordProto.Start.LAST_START_ID, lastStartId);
+ proto.write(ServiceRecordProto.Start.START_COMMAND_RESULT, startCommandResult);
proto.end(startToken);
}
@@ -536,9 +535,22 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN
}
}
}
- proto.end(token);
+ if (mShortFgsInfo != null && mShortFgsInfo.isCurrent()) {
+ final long shortFgsToken = proto.start(ServiceRecordProto.SHORT_FGS_INFO);
+ proto.write(ServiceRecordProto.ShortFgsInfo.START_TIME,
+ mShortFgsInfo.getStartTime());
+ proto.write(ServiceRecordProto.ShortFgsInfo.START_ID,
+ mShortFgsInfo.getStartId());
+ proto.write(ServiceRecordProto.ShortFgsInfo.TIMEOUT_TIME,
+ mShortFgsInfo.getTimeoutTime());
+ proto.write(ServiceRecordProto.ShortFgsInfo.PROC_STATE_DEMOTE_TIME,
+ mShortFgsInfo.getProcStateDemoteTime());
+ proto.write(ServiceRecordProto.ShortFgsInfo.ANR_TIME,
+ mShortFgsInfo.getAnrTime());
+ proto.end(shortFgsToken);
+ }
- // TODO(short-service) Add FGS info
+ proto.end(token);
}
void dump(PrintWriter pw, String prefix) {
@@ -635,6 +647,7 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN
pw.print(" stopIfKilled="); pw.print(stopIfKilled);
pw.print(" callStart="); pw.print(callStart);
pw.print(" lastStartId="); pw.println(lastStartId);
+ pw.print(" startCommandResult="); pw.println(startCommandResult);
}
if (executeNesting != 0) {
pw.print(prefix); pw.print("executeNesting="); pw.print(executeNesting);
@@ -897,7 +910,9 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN
* has no reason to start again. Note this condition doesn't consider the bindings.
*/
boolean canStopIfKilled(boolean isStartCanceled) {
- // TODO(short-service): If it's a "short FGS", we should stop it if killed.
+ if (isShortFgs()) { // Short-FGS should always stop if killed.
+ return true;
+ }
return startRequested && (stopIfKilled || isStartCanceled) && pendingStarts.isEmpty();
}
@@ -1361,7 +1376,9 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN
// Note if the type contains FOREGROUND_SERVICE_TYPE_SHORT_SERVICE but also other bits
// set, it's _not_ considered be a short service. (because we shouldn't apply
// the short-service restrictions)
- return isForeground
+ // (But we should be preventing mixture of FOREGROUND_SERVICE_TYPE_SHORT_SERVICE
+ // and other types in Service.startForeground().)
+ return startRequested && isForeground
&& (foregroundServiceType == ServiceInfo.FOREGROUND_SERVICE_TYPE_SHORT_SERVICE);
}
@@ -1399,7 +1416,7 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN
|| !mShortFgsInfo.isCurrent()) {
return false;
}
- return mShortFgsInfo.getTimeoutTime() < SystemClock.uptimeMillis();
+ return mShortFgsInfo.getTimeoutTime() <= SystemClock.uptimeMillis();
}
/**
@@ -1414,7 +1431,7 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN
|| !mShortFgsInfo.isCurrent()) {
return false;
}
- return mShortFgsInfo.getAnrTime() < SystemClock.uptimeMillis();
+ return mShortFgsInfo.getAnrTime() <= SystemClock.uptimeMillis();
}
private boolean isAppAlive() {
diff --git a/services/core/java/com/android/server/am/TEST_MAPPING b/services/core/java/com/android/server/am/TEST_MAPPING
index 060e3eef88ec..2a693634087b 100644
--- a/services/core/java/com/android/server/am/TEST_MAPPING
+++ b/services/core/java/com/android/server/am/TEST_MAPPING
@@ -18,6 +18,34 @@
]
},
{
+ "name": "CtsAppFgsTestCases",
+ "options": [
+ {
+ "include-annotation": "android.platform.test.annotations.Presubmit"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.LargeTest"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
+ },
+ {
+ "name": "CtsShortFgsTestCases",
+ "options": [
+ {
+ "include-annotation": "android.platform.test.annotations.Presubmit"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.LargeTest"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
+ },
+ {
"name": "FrameworksServicesTests",
"options": [
{
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index af0bd633c471..aefa2f5bf66e 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -80,7 +80,6 @@ import android.os.IBinder;
import android.os.IProgressListener;
import android.os.IRemoteCallback;
import android.os.IUserManager;
-import android.os.Looper;
import android.os.Message;
import android.os.PowerWhitelistManager;
import android.os.Process;
@@ -437,6 +436,12 @@ class UserController implements Handler.Callback {
/** @see #getLastUserUnlockingUptime */
private volatile long mLastUserUnlockingUptime = 0;
+ /**
+ * Pending user starts waiting for shutdown step to complete.
+ */
+ @GuardedBy("mLock")
+ private final List<PendingUserStart> mPendingUserStarts = new ArrayList<>();
+
private final UserLifecycleListener mUserLifecycleListener = new UserLifecycleListener() {
@Override
public void onUserCreated(UserInfo user, Object token) {
@@ -944,9 +949,8 @@ class UserController implements Handler.Callback {
int stopUser(final int userId, final boolean force, boolean allowDelayedLocking,
final IStopUserCallback stopUserCallback, KeyEvictedCallback keyEvictedCallback) {
checkCallingPermission(INTERACT_ACROSS_USERS_FULL, "stopUser");
- if (userId < 0 || userId == UserHandle.USER_SYSTEM) {
- throw new IllegalArgumentException("Can't stop system user " + userId);
- }
+ Preconditions.checkArgument(userId >= 0, "Invalid user id %d", userId);
+
enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, userId);
synchronized (mLock) {
return stopUsersLU(userId, force, allowDelayedLocking, stopUserCallback,
@@ -1073,9 +1077,6 @@ class UserController implements Handler.Callback {
uss.setState(UserState.STATE_STOPPING);
UserManagerInternal userManagerInternal = mInjector.getUserManagerInternal();
userManagerInternal.setUserState(userId, uss.state);
- // TODO(b/239982558): for now we're just updating the user's visibility, but most likely
- // we'll need to remove this call and handle that as part of the user state workflow
- // instead.
userManagerInternal.unassignUserFromDisplayOnStop(userId);
updateStartedUserArrayLU();
@@ -1187,9 +1188,13 @@ class UserController implements Handler.Callback {
} else {
stopped = true;
// User can no longer run.
+ Slogf.i(TAG, "Removing user state from UserController.mStartedUsers for user #"
+ + userId + " as a result of user being stopped");
mStartedUsers.remove(userId);
+
mUserLru.remove(Integer.valueOf(userId));
updateStartedUserArrayLU();
+
if (allowDelayedLocking && !keyEvictedCallbacks.isEmpty()) {
Slogf.wtf(TAG,
"Delayed locking enabled while KeyEvictedCallbacks not empty, userId:"
@@ -1203,7 +1208,10 @@ class UserController implements Handler.Callback {
}
}
if (stopped) {
+ Slogf.i(TAG, "Removing user state from UserManager.mUserStates for user #" + userId
+ + " as a result of user being stopped");
mInjector.getUserManagerInternal().removeUserState(userId);
+
mInjector.activityManagerOnUserStopped(userId);
// Clean up all state and processes associated with the user.
// Kill all the processes for the user.
@@ -1231,10 +1239,13 @@ class UserController implements Handler.Callback {
USER_LIFECYCLE_EVENT_STATE_FINISH);
clearSessionId(userId);
- if (!lockUser) {
- return;
+ if (lockUser) {
+ dispatchUserLocking(userIdToLock, keyEvictedCallbacks);
}
- dispatchUserLocking(userIdToLock, keyEvictedCallbacks);
+
+ // Resume any existing pending user start,
+ // which was paused while the SHUTDOWN flow of the user was in progress.
+ resumePendingUserStarts(userId);
} else {
logUserLifecycleEvent(userId, USER_LIFECYCLE_EVENT_STOP_USER,
USER_LIFECYCLE_EVENT_STATE_NONE);
@@ -1242,6 +1253,31 @@ class UserController implements Handler.Callback {
}
}
+ /**
+ * Resume any existing pending user start for the specified userId which was paused
+ * while the shutdown flow of the user was in progress.
+ * Remove all the handled user starts from mPendingUserStarts.
+ * @param userId the id of the user
+ */
+ private void resumePendingUserStarts(@UserIdInt int userId) {
+ synchronized (mLock) {
+ final List<PendingUserStart> handledUserStarts = new ArrayList<>();
+
+ for (PendingUserStart userStart: mPendingUserStarts) {
+ if (userStart.userId == userId) {
+ Slogf.i(TAG, "resumePendingUserStart for" + userStart);
+ mHandler.post(() -> startUser(userStart.userId,
+ userStart.isForeground, userStart.unlockListener));
+
+ handledUserStarts.add(userStart);
+ }
+ }
+ // remove all the pending user starts which are now handled
+ mPendingUserStarts.removeAll(handledUserStarts);
+ }
+ }
+
+
private void dispatchUserLocking(@UserIdInt int userId,
@Nullable List<KeyEvictedCallback> keyEvictedCallbacks) {
// Evict the user's credential encryption key. Performed on FgThread to make it
@@ -1255,6 +1291,7 @@ class UserController implements Handler.Callback {
}
}
try {
+ Slogf.i(TAG, "Locking CE storage for user #" + userId);
mInjector.getStorageManager().lockUserKey(userId);
} catch (RemoteException re) {
throw re.rethrowAsRuntimeException();
@@ -1505,13 +1542,32 @@ class UserController implements Handler.Callback {
return startUserNoChecks(userId, Display.DEFAULT_DISPLAY, foreground, unlockListener);
}
- // TODO(b/239982558): add javadoc (need to wait until the intents / SystemService callbacks are
- // defined
+ /**
+ * Starts a user in background and make it visible in the given display.
+ *
+ * <p>This call will trigger the usual "user started" lifecycle events (i.e., `SystemService`
+ * callbacks and app intents), plus a call to
+ * {@link UserManagerInternal.UserVisibilityListener#onUserVisibilityChanged(int, boolean)} if
+ * the user visibility changed. Notice that the visibility change is independent of the user
+ * workflow state, and they can mismatch in some corner events (for example, if the user was
+ * already running in the background but not associated with a display, this call for that user
+ * would not trigger any lifecycle event but would trigger {@code onUserVisibilityChanged}).
+ *
+ * <p>See {@link ActivityManager#startUserInBackgroundOnSecondaryDisplay(int, int)} for more
+ * semantics.
+ *
+ * @param userId user to be started
+ * @param displayId display where the user will be visible
+ *
+ * @return whether the user was started
+ */
boolean startUserOnSecondaryDisplay(@UserIdInt int userId, int displayId) {
checkCallingHasOneOfThosePermissions("startUserOnSecondaryDisplay",
MANAGE_USERS, INTERACT_ACROSS_USERS);
// DEFAULT_DISPLAY is used for the current foreground user only
+ // TODO(b/245939659): might need to move this check to UserVisibilityMediator to support
+ // passenger-only screens
Preconditions.checkArgument(displayId != Display.DEFAULT_DISPLAY,
"Cannot use DEFAULT_DISPLAY");
@@ -1519,7 +1575,7 @@ class UserController implements Handler.Callback {
return startUserNoChecks(userId, displayId, /* foreground= */ false,
/* unlockListener= */ null);
} catch (RuntimeException e) {
- Slogf.w(TAG, "startUserOnSecondaryDisplay(%d, %d) failed: %s", userId, displayId, e);
+ Slogf.e(TAG, "startUserOnSecondaryDisplay(%d, %d) failed: %s", userId, displayId, e);
return false;
}
}
@@ -1618,7 +1674,6 @@ class UserController implements Handler.Callback {
return false;
}
- // TODO(b/239982558): might need something similar for bg users on secondary display
if (foreground && isUserSwitchUiEnabled()) {
t.traceBegin("startFreezingScreen");
mInjector.getWindowManager().startFreezingScreen(
@@ -1642,10 +1697,11 @@ class UserController implements Handler.Callback {
updateStartedUserArrayLU();
needStart = true;
updateUmState = true;
- } else if (uss.state == UserState.STATE_SHUTDOWN && !isCallingOnHandlerThread()) {
+ } else if (uss.state == UserState.STATE_SHUTDOWN) {
Slogf.i(TAG, "User #" + userId
- + " is shutting down - will start after full stop");
- mHandler.post(() -> startUser(userId, foreground, unlockListener));
+ + " is shutting down - will start after full shutdown");
+ mPendingUserStarts.add(new PendingUserStart(userId,
+ foreground, unlockListener));
t.traceEnd(); // updateStartedUserArrayStarting
return true;
}
@@ -1674,9 +1730,9 @@ class UserController implements Handler.Callback {
userSwitchUiEnabled = mUserSwitchUiEnabled;
}
mInjector.updateUserConfiguration();
- // TODO(b/244644281): updateProfileRelatedCaches() is called on both if and else
- // parts, ideally it should be moved outside, but for now it's not as there are many
- // calls to external components here afterwards
+ // NOTE: updateProfileRelatedCaches() is called on both if and else parts, ideally
+ // it should be moved outside, but for now it's not as there are many calls to
+ // external components here afterwards
updateProfileRelatedCaches();
mInjector.getWindowManager().setCurrentUser(userId);
mInjector.reportCurWakefulnessUsageEvent();
@@ -1803,10 +1859,6 @@ class UserController implements Handler.Callback {
return true;
}
- private boolean isCallingOnHandlerThread() {
- return Looper.myLooper() == mHandler.getLooper();
- }
-
/**
* Start user, if its not already running, and bring it to foreground.
*/
@@ -3375,6 +3427,32 @@ class UserController implements Handler.Callback {
}
}
+ /**
+ * Helper class for keeping track of user starts which are paused while user's
+ * shutdown is taking place.
+ */
+ private static class PendingUserStart {
+ public final @UserIdInt int userId;
+ public final boolean isForeground;
+ public final IProgressListener unlockListener;
+
+ PendingUserStart(int userId, boolean foreground,
+ IProgressListener unlockListener) {
+ this.userId = userId;
+ this.isForeground = foreground;
+ this.unlockListener = unlockListener;
+ }
+
+ @Override
+ public String toString() {
+ return "PendingUserStart{"
+ + "userId=" + userId
+ + ", isForeground=" + isForeground
+ + ", unlockListener=" + unlockListener
+ + '}';
+ }
+ }
+
@VisibleForTesting
static class Injector {
private final ActivityManagerService mService;
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index cda18b07c02e..46d3ff1273ec 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -524,8 +524,8 @@ public final class GameManagerService extends IGameManagerService.Stub {
private final ArrayMap<Integer, GameModeConfiguration> mModeConfigs = new ArrayMap<>();
// if adding new properties or make any of the below overridable, the method
// copyAndApplyOverride should be updated accordingly
- private boolean mPerfModeOptedIn = false;
- private boolean mBatteryModeOptedIn = false;
+ private boolean mPerfModeOverridden = false;
+ private boolean mBatteryModeOverridden = false;
private boolean mAllowDownscale = true;
private boolean mAllowAngle = true;
private boolean mAllowFpsOverride = true;
@@ -542,8 +542,8 @@ public final class GameManagerService extends IGameManagerService.Stub {
PackageManager.GET_META_DATA, userId);
if (!parseInterventionFromXml(packageManager, ai, packageName)
&& ai.metaData != null) {
- mPerfModeOptedIn = ai.metaData.getBoolean(METADATA_PERFORMANCE_MODE_ENABLE);
- mBatteryModeOptedIn = ai.metaData.getBoolean(METADATA_BATTERY_MODE_ENABLE);
+ mPerfModeOverridden = ai.metaData.getBoolean(METADATA_PERFORMANCE_MODE_ENABLE);
+ mBatteryModeOverridden = ai.metaData.getBoolean(METADATA_BATTERY_MODE_ENABLE);
mAllowDownscale = ai.metaData.getBoolean(METADATA_WM_ALLOW_DOWNSCALE, true);
mAllowAngle = ai.metaData.getBoolean(METADATA_ANGLE_ALLOW_ANGLE, true);
}
@@ -595,9 +595,9 @@ public final class GameManagerService extends IGameManagerService.Stub {
} else {
final TypedArray array = resources.obtainAttributes(attributeSet,
com.android.internal.R.styleable.GameModeConfig);
- mPerfModeOptedIn = array.getBoolean(
+ mPerfModeOverridden = array.getBoolean(
GameModeConfig_supportsPerformanceGameMode, false);
- mBatteryModeOptedIn = array.getBoolean(
+ mBatteryModeOverridden = array.getBoolean(
GameModeConfig_supportsBatteryGameMode,
false);
mAllowDownscale = array.getBoolean(GameModeConfig_allowGameDownscaling,
@@ -610,8 +610,8 @@ public final class GameManagerService extends IGameManagerService.Stub {
}
} catch (NameNotFoundException | XmlPullParserException | IOException ex) {
// set flag back to default values when parsing fails
- mPerfModeOptedIn = false;
- mBatteryModeOptedIn = false;
+ mPerfModeOverridden = false;
+ mBatteryModeOverridden = false;
mAllowDownscale = true;
mAllowAngle = true;
mAllowFpsOverride = true;
@@ -667,8 +667,8 @@ public final class GameManagerService extends IGameManagerService.Stub {
GameModeConfiguration(KeyValueListParser parser) {
mGameMode = parser.getInt(MODE_KEY, GameManager.GAME_MODE_UNSUPPORTED);
- // isGameModeOptedIn() returns if an app will handle all of the changes necessary
- // for a particular game mode. If so, the Android framework (i.e.
+ // willGamePerformOptimizations() returns if an app will handle all of the changes
+ // necessary for a particular game mode. If so, the Android framework (i.e.
// GameManagerService) will not do anything for the app (like window scaling or
// using ANGLE).
mScaling = !mAllowDownscale || willGamePerformOptimizations(mGameMode)
@@ -775,8 +775,8 @@ public final class GameManagerService extends IGameManagerService.Stub {
* "com.android.app.gamemode.battery.enabled" with a value of "true"
*/
public boolean willGamePerformOptimizations(@GameMode int gameMode) {
- return (mBatteryModeOptedIn && gameMode == GameManager.GAME_MODE_BATTERY)
- || (mPerfModeOptedIn && gameMode == GameManager.GAME_MODE_PERFORMANCE);
+ return (mBatteryModeOverridden && gameMode == GameManager.GAME_MODE_BATTERY)
+ || (mPerfModeOverridden && gameMode == GameManager.GAME_MODE_PERFORMANCE);
}
private int getAvailableGameModesBitfield() {
@@ -787,10 +787,10 @@ public final class GameManagerService extends IGameManagerService.Stub {
field |= modeToBitmask(mode);
}
}
- if (mBatteryModeOptedIn) {
+ if (mBatteryModeOverridden) {
field |= modeToBitmask(GameManager.GAME_MODE_BATTERY);
}
- if (mPerfModeOptedIn) {
+ if (mPerfModeOverridden) {
field |= modeToBitmask(GameManager.GAME_MODE_PERFORMANCE);
}
return field;
@@ -814,14 +814,14 @@ public final class GameManagerService extends IGameManagerService.Stub {
}
/**
- * Get an array of a package's opted-in game modes.
+ * Get an array of a package's overridden game modes.
*/
- public @GameMode int[] getOptedInGameModes() {
- if (mBatteryModeOptedIn && mPerfModeOptedIn) {
+ public @GameMode int[] getOverriddenGameModes() {
+ if (mBatteryModeOverridden && mPerfModeOverridden) {
return new int[]{GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_PERFORMANCE};
- } else if (mBatteryModeOptedIn) {
+ } else if (mBatteryModeOverridden) {
return new int[]{GameManager.GAME_MODE_BATTERY};
- } else if (mPerfModeOptedIn) {
+ } else if (mPerfModeOverridden) {
return new int[]{GameManager.GAME_MODE_PERFORMANCE};
} else {
return new int[]{};
@@ -864,18 +864,18 @@ public final class GameManagerService extends IGameManagerService.Stub {
public boolean isActive() {
synchronized (mModeConfigLock) {
- return mModeConfigs.size() > 0 || mBatteryModeOptedIn || mPerfModeOptedIn;
+ return mModeConfigs.size() > 0 || mBatteryModeOverridden || mPerfModeOverridden;
}
}
GamePackageConfiguration copyAndApplyOverride(GamePackageConfiguration overrideConfig) {
GamePackageConfiguration copy = new GamePackageConfiguration(mPackageName);
// if a game mode is overridden, we treat it with the highest priority and reset any
- // opt-in game modes so that interventions are always executed.
- copy.mPerfModeOptedIn = mPerfModeOptedIn && !(overrideConfig != null
+ // overridden game modes so that interventions are always executed.
+ copy.mPerfModeOverridden = mPerfModeOverridden && !(overrideConfig != null
&& overrideConfig.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE)
!= null);
- copy.mBatteryModeOptedIn = mBatteryModeOptedIn && !(overrideConfig != null
+ copy.mBatteryModeOverridden = mBatteryModeOverridden && !(overrideConfig != null
&& overrideConfig.getGameModeConfiguration(GameManager.GAME_MODE_BATTERY)
!= null);
@@ -1092,12 +1092,12 @@ public final class GameManagerService extends IGameManagerService.Stub {
final @GameMode int activeGameMode = getGameModeFromSettingsUnchecked(packageName, userId);
final GamePackageConfiguration config = getConfig(packageName, userId);
if (config != null) {
- final @GameMode int[] optedInGameModes = config.getOptedInGameModes();
+ final @GameMode int[] overriddenGameModes = config.getOverriddenGameModes();
final @GameMode int[] availableGameModes = config.getAvailableGameModes();
GameModeInfo.Builder gameModeInfoBuilder = new GameModeInfo.Builder()
.setActiveGameMode(activeGameMode)
.setAvailableGameModes(availableGameModes)
- .setOptedInGameModes(optedInGameModes)
+ .setOverriddenGameModes(overriddenGameModes)
.setDownscalingAllowed(config.mAllowDownscale)
.setFpsOverrideAllowed(config.mAllowFpsOverride);
for (int gameMode : availableGameModes) {
@@ -2059,7 +2059,7 @@ public final class GameManagerService extends IGameManagerService.Stub {
if (atomTag == FrameworkStatsLog.GAME_MODE_INFO) {
data.add(
FrameworkStatsLog.buildStatsEvent(FrameworkStatsLog.GAME_MODE_INFO, uid,
- gameModesToStatsdGameModes(config.getOptedInGameModes()),
+ gameModesToStatsdGameModes(config.getOverriddenGameModes()),
gameModesToStatsdGameModes(config.getAvailableGameModes())));
} else if (atomTag == FrameworkStatsLog.GAME_MODE_CONFIGURATION) {
for (int gameMode : config.getAvailableGameModes()) {
diff --git a/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java b/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java
index 8d1da71c95c1..587fb0410bca 100644
--- a/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java
+++ b/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java
@@ -218,7 +218,7 @@ public class AppOpsCheckingServiceImpl implements AppOpsCheckingServiceInterface
}
@Override
- public boolean arePackageModesDefault(String packageMode, @UserIdInt int userId) {
+ public boolean arePackageModesDefault(@NonNull String packageMode, @UserIdInt int userId) {
synchronized (mLock) {
ArrayMap<String, SparseIntArray> packageModes = mUserPackageModes.get(userId, null);
if (packageModes == null) {
@@ -490,15 +490,16 @@ public class AppOpsCheckingServiceImpl implements AppOpsCheckingServiceInterface
}
@Override
- public SparseBooleanArray evalForegroundUidOps(int uid, SparseBooleanArray foregroundOps) {
+ public SparseBooleanArray evalForegroundUidOps(int uid,
+ @Nullable SparseBooleanArray foregroundOps) {
synchronized (mLock) {
return evalForegroundOps(mUidModes.get(uid), foregroundOps);
}
}
@Override
- public SparseBooleanArray evalForegroundPackageOps(String packageName,
- SparseBooleanArray foregroundOps, @UserIdInt int userId) {
+ public SparseBooleanArray evalForegroundPackageOps(@NonNull String packageName,
+ @Nullable SparseBooleanArray foregroundOps, @UserIdInt int userId) {
synchronized (mLock) {
ArrayMap<String, SparseIntArray> packageModes = mUserPackageModes.get(userId, null);
return evalForegroundOps(packageModes == null ? null : packageModes.get(packageName),
@@ -537,8 +538,8 @@ public class AppOpsCheckingServiceImpl implements AppOpsCheckingServiceInterface
}
@Override
- public boolean dumpListeners(int dumpOp, int dumpUid, String dumpPackage,
- PrintWriter printWriter) {
+ public boolean dumpListeners(int dumpOp, int dumpUid, @Nullable String dumpPackage,
+ @NonNull PrintWriter printWriter) {
boolean needSep = false;
if (mOpModeWatchers.size() > 0) {
boolean printedHeader = false;
diff --git a/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java b/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java
index d8d0d48965ea..ef3e3685401f 100644
--- a/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java
+++ b/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java
@@ -103,7 +103,7 @@ public interface AppOpsCheckingServiceInterface {
* @param packageName package name.
* @param userId user id associated with the package.
*/
- boolean arePackageModesDefault(String packageName, @UserIdInt int userId);
+ boolean arePackageModesDefault(@NonNull String packageName, @UserIdInt int userId);
/**
* Stop tracking app-op modes for all uid and packages.
@@ -184,7 +184,7 @@ public interface AppOpsCheckingServiceInterface {
* @param foregroundOps boolean array where app-ops that have MODE_FOREGROUND are marked true.
* @return foregroundOps.
*/
- SparseBooleanArray evalForegroundUidOps(int uid, SparseBooleanArray foregroundOps);
+ SparseBooleanArray evalForegroundUidOps(int uid, @Nullable SparseBooleanArray foregroundOps);
/**
* Go over the list of app-ops for the package name and mark app-ops with MODE_FOREGROUND in
@@ -194,8 +194,8 @@ public interface AppOpsCheckingServiceInterface {
* @param userId user id associated with the package.
* @return foregroundOps.
*/
- SparseBooleanArray evalForegroundPackageOps(String packageName,
- SparseBooleanArray foregroundOps, @UserIdInt int userId);
+ SparseBooleanArray evalForegroundPackageOps(@NonNull String packageName,
+ @Nullable SparseBooleanArray foregroundOps, @UserIdInt int userId);
/**
* Dump op mode and package mode listeners and their details.
@@ -205,5 +205,6 @@ public interface AppOpsCheckingServiceInterface {
* @param dumpPackage if not null and if dumpOp is -1, dumps watchers for the package name.
* @param printWriter writer to dump to.
*/
- boolean dumpListeners(int dumpOp, int dumpUid, String dumpPackage, PrintWriter printWriter);
+ boolean dumpListeners(int dumpOp, int dumpUid, @Nullable String dumpPackage,
+ @NonNull PrintWriter printWriter);
}
diff --git a/services/core/java/com/android/server/appop/AppOpsCheckingServiceTracingDecorator.java b/services/core/java/com/android/server/appop/AppOpsCheckingServiceTracingDecorator.java
new file mode 100644
index 000000000000..44360028704e
--- /dev/null
+++ b/services/core/java/com/android/server/appop/AppOpsCheckingServiceTracingDecorator.java
@@ -0,0 +1,279 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.appop;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.AppOpsManager;
+import android.os.Trace;
+import android.util.ArraySet;
+import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
+
+import java.io.PrintWriter;
+
+/**
+ * Surrounds all AppOpsCheckingServiceInterface method calls with Trace.traceBegin and
+ * Trace.traceEnd. These traces are used for performance testing.
+ */
+public class AppOpsCheckingServiceTracingDecorator implements AppOpsCheckingServiceInterface {
+ private static final long TRACE_TAG = Trace.TRACE_TAG_SYSTEM_SERVER;
+ private final AppOpsCheckingServiceInterface mService;
+
+ AppOpsCheckingServiceTracingDecorator(
+ @NonNull AppOpsCheckingServiceInterface appOpsCheckingServiceInterface) {
+ mService = appOpsCheckingServiceInterface;
+ }
+
+ @Override
+ public SparseIntArray getNonDefaultUidModes(int uid) {
+ Trace.traceBegin(TRACE_TAG,
+ "TaggedTracingAppOpsCheckingServiceInterfaceImpl#getNonDefaultUidModes");
+ try {
+ return mService.getNonDefaultUidModes(uid);
+ } finally {
+ Trace.traceEnd(TRACE_TAG);
+ }
+ }
+
+ @Override
+ public int getUidMode(int uid, int op) {
+ Trace.traceBegin(TRACE_TAG, "TaggedTracingAppOpsCheckingServiceInterfaceImpl#getUidMode");
+ try {
+ return mService.getUidMode(uid, op);
+ } finally {
+ Trace.traceEnd(TRACE_TAG);
+ }
+ }
+
+ @Override
+ public boolean setUidMode(int uid, int op, @AppOpsManager.Mode int mode) {
+ Trace.traceBegin(TRACE_TAG, "TaggedTracingAppOpsCheckingServiceInterfaceImpl#setUidMode");
+ try {
+ return mService.setUidMode(uid, op, mode);
+ } finally {
+ Trace.traceEnd(TRACE_TAG);
+ }
+ }
+
+ @Override
+ public int getPackageMode(@NonNull String packageName, int op, @UserIdInt int userId) {
+ Trace.traceBegin(TRACE_TAG,
+ "TaggedTracingAppOpsCheckingServiceInterfaceImpl#getPackageMode");
+ try {
+ return mService.getPackageMode(packageName, op, userId);
+ } finally {
+ Trace.traceEnd(TRACE_TAG);
+ }
+ }
+
+ @Override
+ public void setPackageMode(@NonNull String packageName, int op, @AppOpsManager.Mode int mode,
+ @UserIdInt int userId) {
+ Trace.traceBegin(TRACE_TAG,
+ "TaggedTracingAppOpsCheckingServiceInterfaceImpl#setPackageMode");
+ try {
+ mService.setPackageMode(packageName, op, mode, userId);
+ } finally {
+ Trace.traceEnd(TRACE_TAG);
+ }
+ }
+
+ @Override
+ public boolean removePackage(@NonNull String packageName, @UserIdInt int userId) {
+ Trace.traceBegin(TRACE_TAG,
+ "TaggedTracingAppOpsCheckingServiceInterfaceImpl#removePackage");
+ try {
+ return mService.removePackage(packageName, userId);
+ } finally {
+ Trace.traceEnd(TRACE_TAG);
+ }
+ }
+
+ @Override
+ public void removeUid(int uid) {
+ Trace.traceBegin(TRACE_TAG,
+ "TaggedTracingAppOpsCheckingServiceInterfaceImpl#removeUid");
+ try {
+ mService.removeUid(uid);
+ } finally {
+ Trace.traceEnd(TRACE_TAG);
+ }
+ }
+
+ @Override
+ public boolean areUidModesDefault(int uid) {
+ Trace.traceBegin(TRACE_TAG,
+ "TaggedTracingAppOpsCheckingServiceInterfaceImpl#areUidModesDefault");
+ try {
+ return mService.areUidModesDefault(uid);
+ } finally {
+ Trace.traceEnd(TRACE_TAG);
+ }
+ }
+
+ @Override
+ public boolean arePackageModesDefault(String packageName, @UserIdInt int userId) {
+ Trace.traceBegin(TRACE_TAG,
+ "TaggedTracingAppOpsCheckingServiceInterfaceImpl#arePackageModesDefault");
+ try {
+ return mService.arePackageModesDefault(packageName, userId);
+ } finally {
+ Trace.traceEnd(TRACE_TAG);
+ }
+ }
+
+ @Override
+ public void clearAllModes() {
+ Trace.traceBegin(TRACE_TAG,
+ "TaggedTracingAppOpsCheckingServiceInterfaceImpl#clearAllModes");
+ try {
+ mService.clearAllModes();
+ } finally {
+ Trace.traceEnd(TRACE_TAG);
+ }
+ }
+
+ @Override
+ public void startWatchingOpModeChanged(@NonNull OnOpModeChangedListener changedListener,
+ int op) {
+ Trace.traceBegin(TRACE_TAG,
+ "TaggedTracingAppOpsCheckingServiceInterfaceImpl#startWatchingOpModeChanged");
+ try {
+ mService.startWatchingOpModeChanged(changedListener, op);
+ } finally {
+ Trace.traceEnd(TRACE_TAG);
+ }
+ }
+
+ @Override
+ public void startWatchingPackageModeChanged(@NonNull OnOpModeChangedListener changedListener,
+ @NonNull String packageName) {
+ Trace.traceBegin(TRACE_TAG,
+ "TaggedTracingAppOpsCheckingServiceInterfaceImpl#startWatchingPackageModeChanged");
+ try {
+ mService.startWatchingPackageModeChanged(changedListener, packageName);
+ } finally {
+ Trace.traceEnd(TRACE_TAG);
+ }
+ }
+
+ @Override
+ public void removeListener(@NonNull OnOpModeChangedListener changedListener) {
+ Trace.traceBegin(TRACE_TAG,
+ "TaggedTracingAppOpsCheckingServiceInterfaceImpl#removeListener");
+ try {
+ mService.removeListener(changedListener);
+ } finally {
+ Trace.traceEnd(TRACE_TAG);
+ }
+ }
+
+ @Override
+ public ArraySet<OnOpModeChangedListener> getOpModeChangedListeners(int op) {
+ Trace.traceBegin(TRACE_TAG,
+ "TaggedTracingAppOpsCheckingServiceInterfaceImpl#getOpModeChangedListeners");
+ try {
+ return mService.getOpModeChangedListeners(op);
+ } finally {
+ Trace.traceEnd(TRACE_TAG);
+ }
+ }
+
+ @Override
+ public ArraySet<OnOpModeChangedListener> getPackageModeChangedListeners(
+ @NonNull String packageName) {
+ Trace.traceBegin(TRACE_TAG,
+ "TaggedTracingAppOpsCheckingServiceInterfaceImpl#getPackageModeChangedListeners");
+ try {
+ return mService.getPackageModeChangedListeners(packageName);
+ } finally {
+ Trace.traceEnd(TRACE_TAG);
+ }
+ }
+
+ @Override
+ public void notifyWatchersOfChange(int op, int uid) {
+ Trace.traceBegin(TRACE_TAG,
+ "TaggedTracingAppOpsCheckingServiceInterfaceImpl#notifyWatchersOfChange");
+ try {
+ mService.notifyWatchersOfChange(op, uid);
+ } finally {
+ Trace.traceEnd(TRACE_TAG);
+ }
+ }
+
+ @Override
+ public void notifyOpChanged(@NonNull OnOpModeChangedListener changedListener, int op, int uid,
+ @Nullable String packageName) {
+ Trace.traceBegin(TRACE_TAG,
+ "TaggedTracingAppOpsCheckingServiceInterfaceImpl#notifyOpChanged");
+ try {
+ mService.notifyOpChanged(changedListener, op, uid, packageName);
+ } finally {
+ Trace.traceEnd(TRACE_TAG);
+ }
+ }
+
+ @Override
+ public void notifyOpChangedForAllPkgsInUid(int op, int uid, boolean onlyForeground,
+ @Nullable OnOpModeChangedListener callbackToIgnore) {
+ Trace.traceBegin(TRACE_TAG,
+ "TaggedTracingAppOpsCheckingServiceInterfaceImpl#notifyOpChangedForAllPkgsInUid");
+ try {
+ mService.notifyOpChangedForAllPkgsInUid(op, uid, onlyForeground, callbackToIgnore);
+ } finally {
+ Trace.traceEnd(TRACE_TAG);
+ }
+ }
+
+ @Override
+ public SparseBooleanArray evalForegroundUidOps(int uid, SparseBooleanArray foregroundOps) {
+ Trace.traceBegin(TRACE_TAG,
+ "TaggedTracingAppOpsCheckingServiceInterfaceImpl#evalForegroundUidOps");
+ try {
+ return mService.evalForegroundUidOps(uid, foregroundOps);
+ } finally {
+ Trace.traceEnd(TRACE_TAG);
+ }
+ }
+
+ @Override
+ public SparseBooleanArray evalForegroundPackageOps(String packageName,
+ SparseBooleanArray foregroundOps, @UserIdInt int userId) {
+ Trace.traceBegin(TRACE_TAG,
+ "TaggedTracingAppOpsCheckingServiceInterfaceImpl#evalForegroundPackageOps");
+ try {
+ return mService.evalForegroundPackageOps(packageName, foregroundOps, userId);
+ } finally {
+ Trace.traceEnd(TRACE_TAG);
+ }
+ }
+
+ @Override
+ public boolean dumpListeners(int dumpOp, int dumpUid, String dumpPackage,
+ PrintWriter printWriter) {
+ Trace.traceBegin(TRACE_TAG,
+ "TaggedTracingAppOpsCheckingServiceInterfaceImpl#dumpListeners");
+ try {
+ return mService.dumpListeners(dumpOp, dumpUid, dumpPackage, printWriter);
+ } finally {
+ Trace.traceEnd(TRACE_TAG);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/appop/AppOpsServiceImpl.java b/services/core/java/com/android/server/appop/AppOpsServiceImpl.java
index 70f3bcc64ef0..c3d2717ce795 100644
--- a/services/core/java/com/android/server/appop/AppOpsServiceImpl.java
+++ b/services/core/java/com/android/server/appop/AppOpsServiceImpl.java
@@ -44,6 +44,7 @@ import static android.app.AppOpsManager.OP_PLAY_AUDIO;
import static android.app.AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO;
import static android.app.AppOpsManager.OP_RECORD_AUDIO;
import static android.app.AppOpsManager.OP_RECORD_AUDIO_HOTWORD;
+import static android.app.AppOpsManager.OP_SCHEDULE_EXACT_ALARM;
import static android.app.AppOpsManager.OP_VIBRATE;
import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_FAILED;
import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_STARTED;
@@ -130,6 +131,8 @@ import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.LocalServices;
import com.android.server.LockGuard;
import com.android.server.SystemServerInitThreadPool;
+import com.android.server.pm.UserManagerInternal;
+import com.android.server.pm.permission.PermissionManagerServiceInternal;
import com.android.server.pm.pkg.AndroidPackage;
import com.android.server.pm.pkg.component.ParsedAttribution;
@@ -163,12 +166,30 @@ class AppOpsServiceImpl implements AppOpsServiceInterface {
static final String TAG = "AppOps";
static final boolean DEBUG = false;
+ /**
+ * Sentinel integer version to denote that there was no appops.xml found on boot.
+ * This will happen when a device boots with no existing userdata.
+ */
+ private static final int NO_FILE_VERSION = -2;
+
+ /**
+ * Sentinel integer version to denote that there was no version in the appops.xml found on boot.
+ * This means the file is coming from a build before versioning was added.
+ */
private static final int NO_VERSION = -1;
+
/**
* Increment by one every time and add the corresponding upgrade logic in
- * {@link #upgradeLocked(int)} below. The first version was 1
+ * {@link #upgradeLocked(int)} below. The first version was 1.
*/
- private static final int CURRENT_VERSION = 1;
+ @VisibleForTesting
+ static final int CURRENT_VERSION = 2;
+
+ /**
+ * This stores the version of appops.xml seen at boot. If this is smaller than
+ * {@link #CURRENT_VERSION}, then we will run {@link #upgradeLocked(int)} on startup.
+ */
+ private int mVersionAtBoot = NO_FILE_VERSION;
// Write at most every 30 minutes.
static final long WRITE_DELAY = DEBUG ? 1000 : 30 * 60 * 1000;
@@ -828,8 +849,8 @@ class AppOpsServiceImpl implements AppOpsServiceInterface {
mSwitchedOps.put(switchCode,
ArrayUtils.appendInt(mSwitchedOps.get(switchCode), switchedCode));
}
- mAppOpsServiceInterface =
- new AppOpsCheckingServiceImpl(this, this, handler, context, mSwitchedOps);
+ mAppOpsServiceInterface = new AppOpsCheckingServiceTracingDecorator(
+ new AppOpsCheckingServiceImpl(this, this, handler, context, mSwitchedOps));
mAppOpsRestrictions = new AppOpsRestrictionsImpl(context, handler,
mAppOpsServiceInterface);
@@ -936,6 +957,10 @@ class AppOpsServiceImpl implements AppOpsServiceInterface {
@Override
public void systemReady() {
+ synchronized (this) {
+ upgradeLocked(mVersionAtBoot);
+ }
+
mConstants.startMonitoring(mContext.getContentResolver());
mHistoricalRegistry.systemReady(mContext.getContentResolver());
@@ -3191,7 +3216,6 @@ class AppOpsServiceImpl implements AppOpsServiceInterface {
@Override
public void readState() {
- int oldVersion = NO_VERSION;
synchronized (mFile) {
synchronized (this) {
FileInputStream stream;
@@ -3216,7 +3240,7 @@ class AppOpsServiceImpl implements AppOpsServiceInterface {
throw new IllegalStateException("no start tag found");
}
- oldVersion = parser.getAttributeInt(null, "v", NO_VERSION);
+ mVersionAtBoot = parser.getAttributeInt(null, "v", NO_VERSION);
int outerDepth = parser.getDepth();
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
@@ -3261,12 +3285,11 @@ class AppOpsServiceImpl implements AppOpsServiceInterface {
}
}
}
- synchronized (this) {
- upgradeLocked(oldVersion);
- }
}
- private void upgradeRunAnyInBackgroundLocked() {
+ @VisibleForTesting
+ @GuardedBy("this")
+ void upgradeRunAnyInBackgroundLocked() {
for (int i = 0; i < mUidStates.size(); i++) {
final UidState uidState = mUidStates.valueAt(i);
if (uidState == null) {
@@ -3303,8 +3326,45 @@ class AppOpsServiceImpl implements AppOpsServiceInterface {
}
}
+ /**
+ * The interpretation of the default mode - MODE_DEFAULT - for OP_SCHEDULE_EXACT_ALARM is
+ * changing. Simultaneously, we want to change this op's mode from MODE_DEFAULT to MODE_ALLOWED
+ * for already installed apps. For newer apps, it will stay as MODE_DEFAULT.
+ */
+ @VisibleForTesting
+ @GuardedBy("this")
+ void upgradeScheduleExactAlarmLocked() {
+ final PermissionManagerServiceInternal pmsi = LocalServices.getService(
+ PermissionManagerServiceInternal.class);
+ final UserManagerInternal umi = LocalServices.getService(UserManagerInternal.class);
+ final PackageManagerInternal pmi = getPackageManagerInternal();
+
+ final String[] packagesDeclaringPermission = pmsi.getAppOpPermissionPackages(
+ AppOpsManager.opToPermission(OP_SCHEDULE_EXACT_ALARM));
+ final int[] userIds = umi.getUserIds();
+
+ for (final String pkg : packagesDeclaringPermission) {
+ for (int userId : userIds) {
+ final int uid = pmi.getPackageUid(pkg, 0, userId);
+
+ UidState uidState = mUidStates.get(uid);
+ if (uidState == null) {
+ uidState = new UidState(uid);
+ mUidStates.put(uid, uidState);
+ }
+ final int oldMode = uidState.getUidMode(OP_SCHEDULE_EXACT_ALARM);
+ if (oldMode == AppOpsManager.opToDefaultMode(OP_SCHEDULE_EXACT_ALARM)) {
+ uidState.setUidMode(OP_SCHEDULE_EXACT_ALARM, MODE_ALLOWED);
+ }
+ }
+ // This appop is meant to be controlled at a uid level. So we leave package modes as
+ // they are.
+ }
+ }
+
+ @GuardedBy("this")
private void upgradeLocked(int oldVersion) {
- if (oldVersion >= CURRENT_VERSION) {
+ if (oldVersion == NO_FILE_VERSION || oldVersion >= CURRENT_VERSION) {
return;
}
Slog.d(TAG, "Upgrading app-ops xml from version " + oldVersion + " to " + CURRENT_VERSION);
@@ -3313,6 +3373,9 @@ class AppOpsServiceImpl implements AppOpsServiceInterface {
upgradeRunAnyInBackgroundLocked();
// fall through
case 1:
+ upgradeScheduleExactAlarmLocked();
+ // fall through
+ case 2:
// for future upgrades
}
scheduleFastWriteLocked();
diff --git a/services/core/java/com/android/server/appop/OnOpModeChangedListener.java b/services/core/java/com/android/server/appop/OnOpModeChangedListener.java
index 5ebe8119f046..1d1a9e71cc3d 100644
--- a/services/core/java/com/android/server/appop/OnOpModeChangedListener.java
+++ b/services/core/java/com/android/server/appop/OnOpModeChangedListener.java
@@ -22,7 +22,7 @@ import android.os.RemoteException;
* Listener for mode changes, encapsulates methods that should be triggered in the event of a mode
* change.
*/
-abstract class OnOpModeChangedListener {
+public abstract class OnOpModeChangedListener {
// Constant meaning that any UID should be matched when dispatching callbacks
private static final int UID_ANY = -2;
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 8aa898edff9a..58cf7efb7cda 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -86,6 +86,7 @@ import android.media.AudioDeviceVolumeManager;
import android.media.AudioFocusInfo;
import android.media.AudioFocusRequest;
import android.media.AudioFormat;
+import android.media.AudioHalVersionInfo;
import android.media.AudioManager;
import android.media.AudioManagerInternal;
import android.media.AudioPlaybackConfiguration;
@@ -257,6 +258,9 @@ public class AudioService extends IAudioService.Stub
/** Debug communication route */
protected static final boolean DEBUG_COMM_RTE = false;
+ /** Debug log sound fx (touchsounds...) in dumpsys */
+ protected static final boolean DEBUG_LOG_SOUND_FX = false;
+
/** How long to delay before persisting a change in volume/ringer mode. */
private static final int PERSIST_DELAY = 500;
@@ -378,6 +382,7 @@ public class AudioService extends IAudioService.Stub
private static final int MSG_ROTATION_UPDATE = 48;
private static final int MSG_FOLD_UPDATE = 49;
private static final int MSG_RESET_SPATIALIZER = 50;
+ private static final int MSG_NO_LOG_FOR_PLAYER_I = 51;
// start of messages handled under wakelock
// these messages can only be queued, i.e. sent with queueMsgUnderWakeLock(),
@@ -1016,7 +1021,7 @@ public class AudioService extends IAudioService.Stub
PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
mAudioEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "handleAudioEvent");
- mSfxHelper = new SoundEffectsHelper(mContext);
+ mSfxHelper = new SoundEffectsHelper(mContext, playerBase -> ignorePlayerLogs(playerBase));
final boolean headTrackingDefault = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_spatial_audio_head_tracking_enabled_default);
@@ -1500,6 +1505,18 @@ public class AudioService extends IAudioService.Stub
}
//-----------------------------------------------------------------
+ // Communicate to PlayackActivityMonitor whether to log or not
+ // the sound FX activity (useful for removing touch sounds in the activity logs)
+ void ignorePlayerLogs(@NonNull PlayerBase playerToIgnore) {
+ if (DEBUG_LOG_SOUND_FX) {
+ return;
+ }
+ sendMsg(mAudioHandler, MSG_NO_LOG_FOR_PLAYER_I, SENDMSG_REPLACE,
+ /*arg1, piid of the player*/ playerToIgnore.getPlayerIId(),
+ /*arg2 ignored*/ 0, /*obj ignored*/ null, /*delay*/ 0);
+ }
+
+ //-----------------------------------------------------------------
// monitoring requests for volume range initialization
@Override // AudioSystemAdapter.OnVolRangeInitRequestListener
public void onVolumeRangeInitRequestFromNative() {
@@ -6622,9 +6639,13 @@ public class AudioService extends IAudioService.Stub
return AudioSystem.STREAM_RING;
} else if (wasStreamActiveRecently(
AudioSystem.STREAM_NOTIFICATION, sStreamOverrideDelayMs)) {
- if (DEBUG_VOL)
- Log.v(TAG, "getActiveStreamType: Forcing STREAM_NOTIFICATION stream active");
- return AudioSystem.STREAM_NOTIFICATION;
+ if (DEBUG_VOL) {
+ Log.v(
+ TAG,
+ "getActiveStreamType: Forcing STREAM_NOTIFICATION stream"
+ + " active");
+ }
+ return AudioSystem.STREAM_NOTIFICATION;
} else {
if (DEBUG_VOL) {
Log.v(TAG, "getActiveStreamType: Forcing DEFAULT_VOL_STREAM_NO_PLAYBACK("
@@ -8721,6 +8742,10 @@ public class AudioService extends IAudioService.Stub
// fold parameter format: "device_folded=x" where x is one of on, off
mAudioSystem.setParameters((String) msg.obj);
break;
+
+ case MSG_NO_LOG_FOR_PLAYER_I:
+ mPlaybackMonitor.ignorePlayerIId(msg.arg1);
+ break;
}
}
}
@@ -10913,6 +10938,21 @@ public class AudioService extends IAudioService.Stub
}
/**
+ * Called by an AudioPolicyProxy when the client dies.
+ * Checks if an active playback for media use case is currently routed to one of the
+ * remote submix devices owned by this dynamic policy and broadcasts a becoming noisy
+ * intend in this case.
+ * @param addresses list of remote submix device addresses to check.
+ */
+ private void onPolicyClientDeath(List<String> addresses) {
+ for (String address : addresses) {
+ if (mPlaybackMonitor.hasActiveMediaPlaybackOnSubmixWithAddress(address)) {
+ mDeviceBroker.postBroadcastBecomingNoisy();
+ return;
+ }
+ }
+ }
+ /**
* Apps with MODIFY_AUDIO_ROUTING can register any policy.
* Apps with an audio capable MediaProjection are allowed to register a RENDER|LOOPBACK policy
* as those policy do not modify the audio routing.
@@ -11284,15 +11324,16 @@ public class AudioService extends IAudioService.Stub
return mMediaFocusControl.sendFocusLoss(focusLoser);
}
- private static final String[] HAL_VERSIONS =
- new String[] {"7.1", "7.0", "6.0", "5.0", "4.0", "2.0"};
-
- /** @see AudioManager#getHalVersion */
- public @Nullable String getHalVersion() {
- for (String version : HAL_VERSIONS) {
+ /**
+ * @see AudioManager#getHalVersion
+ */
+ public @Nullable AudioHalVersionInfo getHalVersion() {
+ for (AudioHalVersionInfo version : AudioHalVersionInfo.VERSIONS) {
try {
+ // TODO: check AIDL service.
+ String versionStr = version.getMajorVersion() + "." + version.getMinorVersion();
HwBinder.getService(
- String.format("android.hardware.audio@%s::IDevicesFactory", version),
+ String.format("android.hardware.audio@%s::IDevicesFactory", versionStr),
"default");
return version;
} catch (NoSuchElementException e) {
@@ -11386,8 +11427,8 @@ public class AudioService extends IAudioService.Stub
}
public List<AudioRecordingConfiguration> getActiveRecordingConfigurations() {
- final boolean isPrivileged =
- (PackageManager.PERMISSION_GRANTED == mContext.checkCallingPermission(
+ final boolean isPrivileged = Binder.getCallingUid() == Process.SYSTEM_UID
+ || (PackageManager.PERMISSION_GRANTED == mContext.checkCallingPermission(
android.Manifest.permission.MODIFY_AUDIO_ROUTING));
return mRecordMonitor.getActiveRecordingConfigurations(isPrivileged);
}
@@ -11622,6 +11663,13 @@ public class AudioService extends IAudioService.Stub
public void binderDied() {
mDynPolicyLogger.enqueue((new EventLogger.StringEvent("AudioPolicy "
+ mPolicyCallback.asBinder() + " died").printLog(TAG)));
+
+ List<String> addresses = new ArrayList<>();
+ for (AudioMix mix : mMixes) {
+ addresses.add(mix.getRegistration());
+ }
+ onPolicyClientDeath(addresses);
+
release();
}
diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
index 0bc4b20b4643..f35931ca1837 100644
--- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
@@ -32,6 +32,7 @@ import android.content.Context;
import android.content.pm.PackageManager;
import android.media.AudioAttributes;
import android.media.AudioDeviceAttributes;
+import android.media.AudioDeviceInfo;
import android.media.AudioManager;
import android.media.AudioPlaybackConfiguration;
import android.media.AudioPlaybackConfiguration.PlayerMuteEvent;
@@ -191,6 +192,18 @@ public final class PlaybackActivityMonitor
}
//=================================================================
+ // Player to ignore (only handling single player, designed for ignoring
+ // in the logs one specific player such as the touch sounds player)
+ @GuardedBy("mPlayerLock")
+ private ArrayList<Integer> mDoNotLogPiidList = new ArrayList<>();
+
+ /*package*/ void ignorePlayerIId(int doNotLogPiid) {
+ synchronized (mPlayerLock) {
+ mDoNotLogPiidList.add(doNotLogPiid);
+ }
+ }
+
+ //=================================================================
// Track players and their states
// methods playerAttributes, playerEvent, releasePlayer are all oneway calls
// into AudioService. They trigger synchronous dispatchPlaybackChange() which updates
@@ -314,14 +327,18 @@ public final class PlaybackActivityMonitor
Log.v(TAG, TextUtils.formatSimple("playerEvent(piid=%d, event=%s, eventValue=%d)",
piid, AudioPlaybackConfiguration.playerStateToString(event), eventValue));
}
-
- final boolean change;
+ boolean change;
synchronized(mPlayerLock) {
final AudioPlaybackConfiguration apc = mPlayers.get(new Integer(piid));
if (apc == null) {
return;
}
+ final boolean doNotLog = mDoNotLogPiidList.contains(piid);
+ if (doNotLog && event != AudioPlaybackConfiguration.PLAYER_STATE_RELEASED) {
+ // do not log nor dispatch events for "ignored" players other than the release
+ return;
+ }
sEventLogger.enqueue(new PlayerEvent(piid, event, eventValue));
if (event == AudioPlaybackConfiguration.PLAYER_UPDATE_PORT_ID) {
@@ -338,7 +355,8 @@ public final class PlaybackActivityMonitor
}
}
}
- if (apc.getPlayerType() == AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL) {
+ if (apc.getPlayerType() == AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL
+ && event != AudioPlaybackConfiguration.PLAYER_STATE_RELEASED) {
// FIXME SoundPool not ready for state reporting
return;
}
@@ -350,9 +368,15 @@ public final class PlaybackActivityMonitor
Log.e(TAG, "Error handling event " + event);
change = false;
}
- if (change && event == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
- mDuckingManager.checkDuck(apc);
- mFadingManager.checkFade(apc);
+ if (change) {
+ if (event == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
+ mDuckingManager.checkDuck(apc);
+ mFadingManager.checkFade(apc);
+ }
+ if (doNotLog) {
+ // do not dispatch events for "ignored" players
+ change = false;
+ }
}
}
if (change) {
@@ -435,6 +459,10 @@ public final class PlaybackActivityMonitor
mEventHandler.sendMessage(
mEventHandler.obtainMessage(MSG_L_CLEAR_PORTS_FOR_PIID, piid, /*arg2=*/0));
+ if (change && mDoNotLogPiidList.contains(piid)) {
+ // do not dispatch a change for a "do not log" player
+ change = false;
+ }
}
}
if (change) {
@@ -542,6 +570,26 @@ public final class PlaybackActivityMonitor
return false;
}
+ /**
+ * Return true if an active playback for media use case is currently routed to
+ * a remote submix device with the supplied address.
+ * @param address
+ */
+ public boolean hasActiveMediaPlaybackOnSubmixWithAddress(@NonNull String address) {
+ synchronized (mPlayerLock) {
+ for (AudioPlaybackConfiguration apc : mPlayers.values()) {
+ AudioDeviceInfo device = apc.getAudioDeviceInfo();
+ if (apc.getAudioAttributes().getUsage() == AudioAttributes.USAGE_MEDIA
+ && apc.isActive() && device != null
+ && device.getInternalType() == AudioSystem.DEVICE_OUT_REMOTE_SUBMIX
+ && address.equals(device.getAddress())) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
protected void dump(PrintWriter pw) {
// players
pw.println("\nPlaybackActivityMonitor dump time: "
@@ -560,6 +608,9 @@ public final class PlaybackActivityMonitor
for (Integer piidInt : piidIntList) {
final AudioPlaybackConfiguration apc = mPlayers.get(piidInt);
if (apc != null) {
+ if (mDoNotLogPiidList.contains(apc.getPlayerInterfaceId())) {
+ pw.print("(not logged)");
+ }
apc.dump(pw);
}
}
diff --git a/services/core/java/com/android/server/audio/SoundEffectsHelper.java b/services/core/java/com/android/server/audio/SoundEffectsHelper.java
index 79b54ebfeb3c..8c4efbae0a7c 100644
--- a/services/core/java/com/android/server/audio/SoundEffectsHelper.java
+++ b/services/core/java/com/android/server/audio/SoundEffectsHelper.java
@@ -25,6 +25,7 @@ import android.media.AudioSystem;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnCompletionListener;
import android.media.MediaPlayer.OnErrorListener;
+import android.media.PlayerBase;
import android.media.SoundPool;
import android.os.Environment;
import android.os.Handler;
@@ -47,6 +48,7 @@ import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.function.Consumer;
/**
* A helper class for managing sound effects loading / unloading
@@ -109,11 +111,14 @@ class SoundEffectsHelper {
private final int[] mEffects = new int[AudioManager.NUM_SOUND_EFFECTS]; // indexes in mResources
private SoundPool mSoundPool;
private SoundPoolLoader mSoundPoolLoader;
+ /** callback to provide handle to the player of the sound effects */
+ private final Consumer<PlayerBase> mPlayerAvailableCb;
- SoundEffectsHelper(Context context) {
+ SoundEffectsHelper(Context context, Consumer<PlayerBase> playerAvailableCb) {
mContext = context;
mSfxAttenuationDb = mContext.getResources().getInteger(
com.android.internal.R.integer.config_soundEffectVolumeDb);
+ mPlayerAvailableCb = playerAvailableCb;
startWorker();
}
@@ -189,6 +194,7 @@ class SoundEffectsHelper {
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.build())
.build();
+ mPlayerAvailableCb.accept(mSoundPool);
loadSoundAssets();
mSoundPoolLoader = new SoundPoolLoader();
diff --git a/services/core/java/com/android/server/audio/TEST_MAPPING b/services/core/java/com/android/server/audio/TEST_MAPPING
index f3a73f043c29..d6c2fb27cf76 100644
--- a/services/core/java/com/android/server/audio/TEST_MAPPING
+++ b/services/core/java/com/android/server/audio/TEST_MAPPING
@@ -13,6 +13,17 @@
"include-filter": "android.media.audio.cts.SpatializerTest"
}
]
+ },
+ {
+ "name": "audiopolicytest",
+ "options": [
+ {
+ "include-filter": "com.android.audiopolicytest.AudioPolicyDeathTest"
+ },
+ {
+ "include-annotation": "android.platform.test.annotations.Presubmit"
+ }
+ ]
}
]
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
index 2f147c4252cd..cb409fef6fa2 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
@@ -278,13 +278,6 @@ public class FaceService extends SystemService {
return -1;
}
- if (!Utils.isUserEncryptedOrLockdown(mLockPatternUtils, userId)) {
- // If this happens, something in KeyguardUpdateMonitor is wrong. This should only
- // ever be invoked when the user is encrypted or lockdown.
- Slog.e(TAG, "detectFace invoked when user is not encrypted or lockdown");
- return -1;
- }
-
final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for detectFace");
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index bc9bc031ca35..4fcde97fc772 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -3475,6 +3475,8 @@ public class Vpn {
return;
} else {
mActiveNetwork = null;
+ mUnderlyingNetworkCapabilities = null;
+ mUnderlyingLinkProperties = null;
}
if (mScheduledHandleNetworkLostFuture != null) {
@@ -3664,9 +3666,6 @@ public class Vpn {
scheduleRetryNewIkeSession();
}
- mUnderlyingNetworkCapabilities = null;
- mUnderlyingLinkProperties = null;
-
// Close all obsolete state, but keep VPN alive incase a usable network comes up.
// (Mirrors VpnService behavior)
Log.d(TAG, "Resetting state for token: " + mCurrentToken);
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index eb81e70363d4..dcc98e17fadf 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -27,6 +27,7 @@ import android.accounts.AccountManager;
import android.accounts.AccountManagerInternal;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.annotation.UserIdInt;
import android.app.ActivityManagerInternal;
import android.app.AppGlobals;
@@ -65,6 +66,7 @@ import android.content.pm.RegisteredServicesCache;
import android.content.pm.RegisteredServicesCacheListener;
import android.content.pm.ResolveInfo;
import android.content.pm.UserInfo;
+import android.content.pm.UserProperties;
import android.database.ContentObserver;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
@@ -88,6 +90,7 @@ import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.WorkSource;
+import android.provider.ContactsContract;
import android.provider.Settings;
import android.text.TextUtils;
import android.text.format.TimeMigrationUtils;
@@ -99,6 +102,7 @@ import android.util.SparseBooleanArray;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IBatteryStats;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.notification.SystemNotificationChannels;
@@ -498,7 +502,7 @@ public class SyncManager {
}
mJobScheduler = (JobScheduler) mContext.getSystemService(
Context.JOB_SCHEDULER_SERVICE);
- mJobSchedulerInternal = LocalServices.getService(JobSchedulerInternal.class);
+ mJobSchedulerInternal = getJobSchedulerInternal();
// Get all persisted syncs from JobScheduler
List<JobInfo> pendingJobs = mJobScheduler.getAllPendingJobs();
@@ -536,6 +540,11 @@ public class SyncManager {
}
}
+ @VisibleForTesting
+ protected JobSchedulerInternal getJobSchedulerInternal() {
+ return LocalServices.getService(JobSchedulerInternal.class);
+ }
+
/**
* @return whether the device most likely has some periodic syncs.
*/
@@ -645,7 +654,7 @@ public class SyncManager {
mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
mAccountManager = (AccountManager) mContext.getSystemService(Context.ACCOUNT_SERVICE);
- mAccountManagerInternal = LocalServices.getService(AccountManagerInternal.class);
+ mAccountManagerInternal = getAccountManagerInternal();
mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
mAmi = LocalServices.getService(ActivityManagerInternal.class);
@@ -719,6 +728,11 @@ public class SyncManager {
mLogger.log("Sync manager initialized: " + Build.FINGERPRINT);
}
+ @VisibleForTesting
+ protected AccountManagerInternal getAccountManagerInternal() {
+ return LocalServices.getService(AccountManagerInternal.class);
+ }
+
public void onStartUser(int userId) {
// Log on the handler to avoid slowing down device boot.
mSyncHandler.post(() -> mLogger.log("onStartUser: user=", userId));
@@ -800,9 +814,44 @@ public class SyncManager {
return mSyncStorageEngine;
}
+ @SuppressLint("AndroidFrameworkRequiresPermission")
+ private boolean areContactWritesEnabledForUser(UserInfo userInfo) {
+ final UserManager um = UserManager.get(mContext);
+ try {
+ final UserProperties userProperties = um.getUserProperties(userInfo.getUserHandle());
+ return !userProperties.getUseParentsContacts();
+ } catch (IllegalArgumentException e) {
+ Log.w(TAG, "Trying to fetch user properties for non-existing/partial user "
+ + userInfo.getUserHandle());
+ return false;
+ }
+ }
+
+ /**
+ * Check if account sync should be disabled for the given user and provider.
+ * @param userInfo
+ * @param providerName
+ * @return true if sync for the account corresponding to the given user and provider should be
+ * disabled, false otherwise. Also returns false if either of the inputs are null.
+ */
+ @VisibleForTesting
+ protected boolean shouldDisableSyncForUser(UserInfo userInfo, String providerName) {
+ if (userInfo == null || providerName == null) return false;
+ return providerName.equals(ContactsContract.AUTHORITY)
+ && !areContactWritesEnabledForUser(userInfo);
+ }
+
private int getIsSyncable(Account account, int userId, String providerName) {
int isSyncable = mSyncStorageEngine.getIsSyncable(account, userId, providerName);
- UserInfo userInfo = UserManager.get(mContext).getUserInfo(userId);
+ final UserManager um = UserManager.get(mContext);
+ UserInfo userInfo = um.getUserInfo(userId);
+
+ // Check if the provider is allowed to sync data from linked accounts for the user
+ if (shouldDisableSyncForUser(userInfo, providerName)) {
+ Log.w(TAG, "Account sync is disabled for account: " + account
+ + " userId: " + userId + " provider: " + providerName);
+ return AuthorityInfo.NOT_SYNCABLE;
+ }
// If it's not a restricted user, return isSyncable.
if (userInfo == null || !userInfo.isRestricted()) return isSyncable;
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index 7b60421850d6..197c64ecd03f 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -483,8 +483,7 @@ class AutomaticBrightnessController {
private static boolean isInteractivePolicy(int policy) {
return policy == DisplayPowerRequest.POLICY_BRIGHT
- || policy == DisplayPowerRequest.POLICY_DIM
- || policy == DisplayPowerRequest.POLICY_VR;
+ || policy == DisplayPowerRequest.POLICY_DIM;
}
private boolean setScreenBrightnessByUser(float brightness) {
@@ -603,6 +602,14 @@ class AutomaticBrightnessController {
mAmbientBrightnessThresholdsIdle.dump(pw);
}
+ public float[] getLastSensorValues() {
+ return mAmbientLightRingBuffer.getAllLuxValues();
+ }
+
+ public long[] getLastSensorTimestamps() {
+ return mAmbientLightRingBuffer.getAllTimestamps();
+ }
+
private String configStateToString(int state) {
switch (state) {
case AUTO_BRIGHTNESS_ENABLED:
@@ -1232,10 +1239,42 @@ class AutomaticBrightnessController {
return mRingLux[offsetOf(index)];
}
+ public float[] getAllLuxValues() {
+ float[] values = new float[mCount];
+ if (mCount == 0) {
+ return values;
+ }
+
+ if (mStart < mEnd) {
+ System.arraycopy(mRingLux, mStart, values, 0, mCount);
+ } else {
+ System.arraycopy(mRingLux, mStart, values, 0, mCapacity - mStart);
+ System.arraycopy(mRingLux, 0, values, mCapacity - mStart, mEnd);
+ }
+
+ return values;
+ }
+
public long getTime(int index) {
return mRingTime[offsetOf(index)];
}
+ public long[] getAllTimestamps() {
+ long[] values = new long[mCount];
+ if (mCount == 0) {
+ return values;
+ }
+
+ if (mStart < mEnd) {
+ System.arraycopy(mRingTime, mStart, values, 0, mCount);
+ } else {
+ System.arraycopy(mRingTime, mStart, values, 0, mCapacity - mStart);
+ System.arraycopy(mRingTime, 0, values, mCapacity - mStart, mEnd);
+ }
+
+ return values;
+ }
+
public void push(long time, float lux) {
int next = mEnd;
if (mCount == mCapacity) {
diff --git a/services/core/java/com/android/server/display/BrightnessTracker.java b/services/core/java/com/android/server/display/BrightnessTracker.java
index df4c471938ed..6e1640d545fe 100644
--- a/services/core/java/com/android/server/display/BrightnessTracker.java
+++ b/services/core/java/com/android/server/display/BrightnessTracker.java
@@ -79,10 +79,8 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
-import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Date;
-import java.util.Deque;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@@ -101,8 +99,6 @@ public class BrightnessTracker {
private static final int MAX_EVENTS = 100;
// Discard events when reading or writing that are older than this.
private static final long MAX_EVENT_AGE = TimeUnit.DAYS.toMillis(30);
- // Time over which we keep lux sensor readings.
- private static final long LUX_EVENT_HORIZON = TimeUnit.SECONDS.toNanos(10);
private static final String TAG_EVENTS = "events";
private static final String TAG_EVENT = "event";
@@ -174,8 +170,6 @@ public class BrightnessTracker {
// Lock held while collecting data related to brightness changes.
private final Object mDataCollectionLock = new Object();
@GuardedBy("mDataCollectionLock")
- private Deque<LightData> mLastSensorReadings = new ArrayDeque<>();
- @GuardedBy("mDataCollectionLock")
private float mLastBatteryLevel = Float.NaN;
@GuardedBy("mDataCollectionLock")
private float mLastBrightness = -1;
@@ -327,7 +321,8 @@ public class BrightnessTracker {
*/
public void notifyBrightnessChanged(float brightness, boolean userInitiated,
float powerBrightnessFactor, boolean isUserSetBrightness,
- boolean isDefaultBrightnessConfig, String uniqueDisplayId) {
+ boolean isDefaultBrightnessConfig, String uniqueDisplayId, float[] luxValues,
+ long[] luxTimestamps) {
if (DEBUG) {
Slog.d(TAG, String.format("notifyBrightnessChanged(brightness=%f, userInitiated=%b)",
brightness, userInitiated));
@@ -335,7 +330,7 @@ public class BrightnessTracker {
Message m = mBgHandler.obtainMessage(MSG_BRIGHTNESS_CHANGED,
userInitiated ? 1 : 0, 0 /*unused*/, new BrightnessChangeValues(brightness,
powerBrightnessFactor, isUserSetBrightness, isDefaultBrightnessConfig,
- mInjector.currentTimeMillis(), uniqueDisplayId));
+ mInjector.currentTimeMillis(), uniqueDisplayId, luxValues, luxTimestamps));
m.sendToTarget();
}
@@ -349,7 +344,8 @@ public class BrightnessTracker {
private void handleBrightnessChanged(float brightness, boolean userInitiated,
float powerBrightnessFactor, boolean isUserSetBrightness,
- boolean isDefaultBrightnessConfig, long timestamp, String uniqueDisplayId) {
+ boolean isDefaultBrightnessConfig, long timestamp, String uniqueDisplayId,
+ float[] luxValues, long[] luxTimestamps) {
BrightnessChangeEvent.Builder builder;
synchronized (mDataCollectionLock) {
@@ -376,28 +372,22 @@ public class BrightnessTracker {
builder.setIsDefaultBrightnessConfig(isDefaultBrightnessConfig);
builder.setUniqueDisplayId(uniqueDisplayId);
- final int readingCount = mLastSensorReadings.size();
- if (readingCount == 0) {
+ if (luxValues.length == 0) {
// No sensor data so ignore this.
return;
}
- float[] luxValues = new float[readingCount];
- long[] luxTimestamps = new long[readingCount];
-
- int pos = 0;
+ long[] luxTimestampsMillis = new long[luxTimestamps.length];
- // Convert sensor timestamp in elapsed time nanos to current time millis.
+ // Convert lux timestamp in elapsed time to current time.
long currentTimeMillis = mInjector.currentTimeMillis();
long elapsedTimeNanos = mInjector.elapsedRealtimeNanos();
- for (LightData reading : mLastSensorReadings) {
- luxValues[pos] = reading.lux;
- luxTimestamps[pos] = currentTimeMillis -
- TimeUnit.NANOSECONDS.toMillis(elapsedTimeNanos - reading.timestamp);
- ++pos;
+ for (int i = 0; i < luxTimestamps.length; i++) {
+ luxTimestampsMillis[i] = currentTimeMillis - (TimeUnit.NANOSECONDS.toMillis(
+ elapsedTimeNanos) - luxTimestamps[i]);
}
builder.setLuxValues(luxValues);
- builder.setLuxTimestamps(luxTimestamps);
+ builder.setLuxTimestamps(luxTimestampsMillis);
builder.setBatteryLevel(mLastBatteryLevel);
builder.setLastBrightness(previousBrightness);
@@ -452,9 +442,6 @@ public class BrightnessTracker {
if (mLightSensor != lightSensor) {
mLightSensor = lightSensor;
stopSensorListener();
- synchronized (mDataCollectionLock) {
- mLastSensorReadings.clear();
- }
// Attempt to restart the sensor listener. It will check to see if it should be running
// so there is no need to also check here.
startSensorListener();
@@ -798,12 +785,6 @@ public class BrightnessTracker {
pw.println(" mLightSensor=" + mLightSensor);
pw.println(" mLastBatteryLevel=" + mLastBatteryLevel);
pw.println(" mLastBrightness=" + mLastBrightness);
- pw.println(" mLastSensorReadings.size=" + mLastSensorReadings.size());
- if (!mLastSensorReadings.isEmpty()) {
- pw.println(" mLastSensorReadings time span "
- + mLastSensorReadings.peekFirst().timestamp + "->"
- + mLastSensorReadings.peekLast().timestamp);
- }
}
synchronized (mEventsLock) {
pw.println(" mEventsDirty=" + mEventsDirty);
@@ -919,43 +900,6 @@ public class BrightnessTracker {
return ParceledListSlice.emptyList();
}
- // Not allowed to keep the SensorEvent so used to copy the data we care about.
- private static class LightData {
- public float lux;
- // Time in elapsedRealtimeNanos
- public long timestamp;
- }
-
- private void recordSensorEvent(SensorEvent event) {
- long horizon = mInjector.elapsedRealtimeNanos() - LUX_EVENT_HORIZON;
- synchronized (mDataCollectionLock) {
- if (DEBUG) {
- Slog.v(TAG, "Sensor event " + event);
- }
- if (!mLastSensorReadings.isEmpty()
- && event.timestamp < mLastSensorReadings.getLast().timestamp) {
- // Ignore event that came out of order.
- return;
- }
- LightData data = null;
- while (!mLastSensorReadings.isEmpty()
- && mLastSensorReadings.getFirst().timestamp < horizon) {
- // Remove data that has fallen out of the window.
- data = mLastSensorReadings.removeFirst();
- }
- // We put back the last one we removed so we know how long
- // the first sensor reading was valid for.
- if (data != null) {
- mLastSensorReadings.addFirst(data);
- }
-
- data = new LightData();
- data.timestamp = event.timestamp;
- data.lux = event.values[0];
- mLastSensorReadings.addLast(data);
- }
- }
-
private void recordAmbientBrightnessStats(SensorEvent event) {
mAmbientBrightnessStatsTracker.add(mCurrentUserId, event.values[0]);
}
@@ -969,7 +913,6 @@ public class BrightnessTracker {
private final class SensorListener implements SensorEventListener {
@Override
public void onSensorChanged(SensorEvent event) {
- recordSensorEvent(event);
recordAmbientBrightnessStats(event);
}
@@ -1056,7 +999,7 @@ public class BrightnessTracker {
handleBrightnessChanged(values.brightness, userInitiatedChange,
values.powerBrightnessFactor, values.isUserSetBrightness,
values.isDefaultBrightnessConfig, values.timestamp,
- values.uniqueDisplayId);
+ values.uniqueDisplayId, values.luxValues, values.luxTimestamps);
break;
case MSG_START_SENSOR_LISTENER:
startSensorListener();
@@ -1092,16 +1035,20 @@ public class BrightnessTracker {
public final boolean isDefaultBrightnessConfig;
public final long timestamp;
public final String uniqueDisplayId;
+ public final float[] luxValues;
+ public final long[] luxTimestamps;
BrightnessChangeValues(float brightness, float powerBrightnessFactor,
boolean isUserSetBrightness, boolean isDefaultBrightnessConfig,
- long timestamp, String uniqueDisplayId) {
+ long timestamp, String uniqueDisplayId, float[] luxValues, long[] luxTimestamps) {
this.brightness = brightness;
this.powerBrightnessFactor = powerBrightnessFactor;
this.isUserSetBrightness = isUserSetBrightness;
this.isDefaultBrightnessConfig = isDefaultBrightnessConfig;
this.timestamp = timestamp;
this.uniqueDisplayId = uniqueDisplayId;
+ this.luxValues = luxValues;
+ this.luxTimestamps = luxTimestamps;
}
}
diff --git a/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
index 9dd2f8408c56..fc6403d2dfb7 100644
--- a/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
+++ b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
@@ -16,6 +16,7 @@
package com.android.server.display;
+import android.annotation.NonNull;
import android.hardware.devicestate.DeviceStateManager;
import android.os.Environment;
import android.util.IndentingPrintWriter;
@@ -23,8 +24,10 @@ import android.util.Slog;
import android.util.SparseArray;
import android.view.DisplayAddress;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.display.config.layout.Layouts;
import com.android.server.display.config.layout.XmlParser;
+import com.android.server.display.layout.DisplayIdProducer;
import com.android.server.display.layout.Layout;
import org.xmlpull.v1.XmlPullParserException;
@@ -48,13 +51,28 @@ class DeviceStateToLayoutMap {
public static final int STATE_DEFAULT = DeviceStateManager.INVALID_DEVICE_STATE;
+ // Direction of the display relative to the default display, whilst in this state
+ private static final int POSITION_UNKNOWN = Layout.Display.POSITION_UNKNOWN;
+ private static final int POSITION_FRONT = Layout.Display.POSITION_FRONT;
+ private static final int POSITION_REAR = Layout.Display.POSITION_REAR;
+
+ private static final String FRONT_STRING = "front";
+ private static final String REAR_STRING = "rear";
+
private static final String CONFIG_FILE_PATH =
"etc/displayconfig/display_layout_configuration.xml";
private final SparseArray<Layout> mLayoutMap = new SparseArray<>();
+ private final DisplayIdProducer mIdProducer;
+
+ DeviceStateToLayoutMap(DisplayIdProducer idProducer) {
+ this(idProducer, Environment.buildPath(
+ Environment.getVendorDirectory(), CONFIG_FILE_PATH));
+ }
- DeviceStateToLayoutMap() {
- loadLayoutsFromConfig();
+ DeviceStateToLayoutMap(DisplayIdProducer idProducer, File configFile) {
+ mIdProducer = idProducer;
+ loadLayoutsFromConfig(configFile);
createLayout(STATE_DEFAULT);
}
@@ -76,24 +94,11 @@ class DeviceStateToLayoutMap {
return layout;
}
- private Layout createLayout(int state) {
- if (mLayoutMap.contains(state)) {
- Slog.e(TAG, "Attempted to create a second layout for state " + state);
- return null;
- }
-
- final Layout layout = new Layout();
- mLayoutMap.append(state, layout);
- return layout;
- }
-
/**
* Reads display-layout-configuration files to get the layouts to use for this device.
*/
- private void loadLayoutsFromConfig() {
- final File configFile = Environment.buildPath(
- Environment.getVendorDirectory(), CONFIG_FILE_PATH);
-
+ @VisibleForTesting
+ void loadLayoutsFromConfig(@NonNull File configFile) {
if (!configFile.exists()) {
return;
}
@@ -109,10 +114,19 @@ class DeviceStateToLayoutMap {
final int state = l.getState().intValue();
final Layout layout = createLayout(state);
for (com.android.server.display.config.layout.Display d: l.getDisplay()) {
- layout.createDisplayLocked(
+ Layout.Display display = layout.createDisplayLocked(
DisplayAddress.fromPhysicalDisplayId(d.getAddress().longValue()),
d.isDefaultDisplay(),
- d.isEnabled());
+ d.isEnabled(),
+ mIdProducer);
+
+ if (FRONT_STRING.equals(d.getPosition())) {
+ display.setPosition(POSITION_FRONT);
+ } else if (REAR_STRING.equals(d.getPosition())) {
+ display.setPosition(POSITION_REAR);
+ } else {
+ display.setPosition(POSITION_UNKNOWN);
+ }
}
}
} catch (IOException | DatatypeConfigurationException | XmlPullParserException e) {
@@ -120,4 +134,15 @@ class DeviceStateToLayoutMap {
+ configFile, e);
}
}
+
+ private Layout createLayout(int state) {
+ if (mLayoutMap.contains(state)) {
+ Slog.e(TAG, "Attempted to create a second layout for state " + state);
+ return null;
+ }
+
+ final Layout layout = new Layout();
+ mLayoutMap.append(state, layout);
+ return layout;
+ }
}
diff --git a/services/core/java/com/android/server/display/DisplayAdapter.java b/services/core/java/com/android/server/display/DisplayAdapter.java
index 1fc151221b46..4f1df3fb996e 100644
--- a/services/core/java/com/android/server/display/DisplayAdapter.java
+++ b/services/core/java/com/android/server/display/DisplayAdapter.java
@@ -120,13 +120,14 @@ abstract class DisplayAdapter {
}
public static Display.Mode createMode(int width, int height, float refreshRate) {
- return createMode(width, height, refreshRate, new float[0]);
+ return createMode(width, height, refreshRate, new float[0], new int[0]);
}
public static Display.Mode createMode(int width, int height, float refreshRate,
- float[] alternativeRefreshRates) {
+ float[] alternativeRefreshRates,
+ @Display.HdrCapabilities.HdrType int[] supportedHdrTypes) {
return new Display.Mode(NEXT_DISPLAY_MODE_ID.getAndIncrement(), width, height, refreshRate,
- alternativeRefreshRates);
+ alternativeRefreshRates, supportedHdrTypes);
}
public interface Listener {
diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
index 881199964d45..fe1d1a693a32 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
@@ -145,7 +145,7 @@ final class DisplayDeviceInfo {
/**
* Flag: Indicates that the display should always be unlocked. Only valid on virtual displays
* that aren't in the default display group.
- * @see #FLAG_OWN_DISPLAY_GROUP
+ * @see #FLAG_OWN_DISPLAY_GROUP and #FLAG_DEVICE_DISPLAY_GROUP
* @hide
*/
public static final int FLAG_ALWAYS_UNLOCKED = 1 << 15;
@@ -172,6 +172,14 @@ final class DisplayDeviceInfo {
public static final int FLAG_OWN_FOCUS = 1 << 17;
/**
+ * Flag: indicates that the display should not be a part of the default {@link DisplayGroup} and
+ * instead be part of a {@link DisplayGroup} associated with the Virtual Device.
+ *
+ * @hide
+ */
+ public static final int FLAG_DEVICE_DISPLAY_GROUP = 1 << 18;
+
+ /**
* Touch attachment: Display does not receive touch.
*/
public static final int TOUCH_NONE = 0;
@@ -237,6 +245,12 @@ final class DisplayDeviceInfo {
public int modeId;
/**
+ * The render frame rate this display is scheduled at.
+ * @see android.view.DisplayInfo#renderFrameRate for more details.
+ */
+ public float renderFrameRate;
+
+ /**
* The default mode of the display.
*/
public int defaultModeId;
@@ -431,6 +445,7 @@ final class DisplayDeviceInfo {
|| width != other.width
|| height != other.height
|| modeId != other.modeId
+ || renderFrameRate != other.renderFrameRate
|| defaultModeId != other.defaultModeId
|| !Arrays.equals(supportedModes, other.supportedModes)
|| !Arrays.equals(supportedColorModes, other.supportedColorModes)
@@ -475,6 +490,7 @@ final class DisplayDeviceInfo {
width = other.width;
height = other.height;
modeId = other.modeId;
+ renderFrameRate = other.renderFrameRate;
defaultModeId = other.defaultModeId;
supportedModes = other.supportedModes;
colorMode = other.colorMode;
@@ -515,6 +531,7 @@ final class DisplayDeviceInfo {
sb.append(name).append("\": uniqueId=\"").append(uniqueId).append("\", ");
sb.append(width).append(" x ").append(height);
sb.append(", modeId ").append(modeId);
+ sb.append(", renderFrameRate ").append(renderFrameRate);
sb.append(", defaultModeId ").append(defaultModeId);
sb.append(", supportedModes ").append(Arrays.toString(supportedModes));
sb.append(", colorMode ").append(colorMode);
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 1d04f2ef99c0..ae84e96f4c2f 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -25,6 +25,7 @@ import static android.hardware.display.DisplayManager.EventsMask;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_DEVICE_DISPLAY_GROUP;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
@@ -35,6 +36,7 @@ import static android.hardware.display.DisplayManagerGlobal.DisplayEvent;
import static android.hardware.display.DisplayViewport.VIEWPORT_EXTERNAL;
import static android.hardware.display.DisplayViewport.VIEWPORT_INTERNAL;
import static android.hardware.display.DisplayViewport.VIEWPORT_VIRTUAL;
+import static android.os.Process.FIRST_APPLICATION_UID;
import static android.os.Process.ROOT_UID;
import android.Manifest;
@@ -881,20 +883,27 @@ public final class DisplayManagerService extends SystemService {
private DisplayInfo getDisplayInfoForFrameRateOverride(DisplayEventReceiver.FrameRateOverride[]
frameRateOverrides, DisplayInfo info, int callingUid) {
- float frameRateHz = 0;
+ float frameRateHz = info.renderFrameRate;
for (DisplayEventReceiver.FrameRateOverride frameRateOverride : frameRateOverrides) {
if (frameRateOverride.uid == callingUid) {
frameRateHz = frameRateOverride.frameRateHz;
break;
}
}
+
if (frameRateHz == 0) {
return info;
}
+ // For non-apps users we always return the physical refresh rate from display mode
+ boolean displayModeReturnsPhysicalRefreshRate =
+ callingUid < FIRST_APPLICATION_UID
+ || CompatChanges.isChangeEnabled(
+ DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE, callingUid);
+
// Override the refresh rate only if it is a divisor of the current
// refresh rate. This calculation needs to be in sync with the native code
- // in RefreshRateConfigs::getFrameRateDivisor
+ // in RefreshRateSelector::getFrameRateDivisor
Display.Mode currentMode = info.getMode();
float numPeriods = currentMode.getRefreshRate() / frameRateHz;
float numPeriodsRound = Math.round(numPeriods);
@@ -918,8 +927,7 @@ public final class DisplayManagerService extends SystemService {
}
overriddenInfo.refreshRateOverride = mode.getRefreshRate();
- if (!CompatChanges.isChangeEnabled(DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE,
- callingUid)) {
+ if (!displayModeReturnsPhysicalRefreshRate) {
overriddenInfo.modeId = mode.getModeId();
}
return overriddenInfo;
@@ -927,14 +935,14 @@ public final class DisplayManagerService extends SystemService {
}
overriddenInfo.refreshRateOverride = frameRateHz;
- if (!CompatChanges.isChangeEnabled(DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE,
- callingUid)) {
+ if (!displayModeReturnsPhysicalRefreshRate) {
overriddenInfo.supportedModes = Arrays.copyOf(info.supportedModes,
info.supportedModes.length + 1);
overriddenInfo.supportedModes[overriddenInfo.supportedModes.length - 1] =
new Display.Mode(Display.DISPLAY_MODE_ID_FOR_FRAME_RATE_OVERRIDE,
currentMode.getPhysicalWidth(), currentMode.getPhysicalHeight(),
- overriddenInfo.refreshRateOverride);
+ overriddenInfo.refreshRateOverride,
+ new float[0], currentMode.getSupportedHdrTypes());
overriddenInfo.modeId =
overriddenInfo.supportedModes[overriddenInfo.supportedModes.length - 1]
.getModeId();
@@ -1275,6 +1283,9 @@ public final class DisplayManagerService extends SystemService {
if ((flags & VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR) != 0) {
flags &= ~VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP;
}
+ if ((flags & VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP) == 0 && virtualDevice != null) {
+ flags |= VIRTUAL_DISPLAY_FLAG_DEVICE_DISPLAY_GROUP;
+ }
if (projection != null) {
try {
@@ -1402,7 +1413,7 @@ public final class DisplayManagerService extends SystemService {
// If the display is to be added to a device display group, we need to make the
// LogicalDisplayMapper aware of the link between the new display and its associated virtual
// device before triggering DISPLAY_DEVICE_EVENT_ADDED.
- if (virtualDevice != null && (flags & VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP) == 0) {
+ if ((flags & VIRTUAL_DISPLAY_FLAG_DEVICE_DISPLAY_GROUP) != 0) {
try {
final int virtualDeviceId = virtualDevice.getDeviceId();
mLogicalDisplayMapper.associateDisplayDeviceWithVirtualDevice(
diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java
index 40e7c5062a77..405a2b958dd1 100644
--- a/services/core/java/com/android/server/display/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/DisplayModeDirector.java
@@ -49,7 +49,7 @@ import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.provider.DeviceConfigInterface;
import android.provider.Settings;
-import android.sysprop.DisplayProperties;
+import android.sysprop.SurfaceFlingerProperties;
import android.text.TextUtils;
import android.util.IndentingPrintWriter;
import android.util.Pair;
@@ -138,8 +138,7 @@ public class DisplayModeDirector {
private boolean mAlwaysRespectAppRequest;
- // TODO(b/241447632): remove the flag once SF changes are ready
- private final boolean mRenderFrameRateIsPhysicalRefreshRate;
+ private final boolean mSupportsFrameRateOverride;
/**
* The allowed refresh rate switching type. This is used by SurfaceFlinger.
@@ -176,7 +175,7 @@ public class DisplayModeDirector {
mHbmObserver = new HbmObserver(injector, ballotBox, BackgroundThread.getHandler(),
mDeviceConfigDisplaySettings);
mAlwaysRespectAppRequest = false;
- mRenderFrameRateIsPhysicalRefreshRate = injector.renderFrameRateIsPhysicalRefreshRate();
+ mSupportsFrameRateOverride = injector.supportsFrameRateOverride();
}
/**
@@ -238,21 +237,6 @@ public class DisplayModeDirector {
}
}
- if (mRenderFrameRateIsPhysicalRefreshRate) {
- for (int i = 0; i < votes.size(); i++) {
-
- Vote vote = votes.valueAt(i);
- vote.refreshRateRanges.physical.min = Math.max(vote.refreshRateRanges.physical.min,
- vote.refreshRateRanges.render.min);
- vote.refreshRateRanges.physical.max = Math.min(vote.refreshRateRanges.physical.max,
- vote.refreshRateRanges.render.max);
- vote.refreshRateRanges.render.min = Math.max(vote.refreshRateRanges.physical.min,
- vote.refreshRateRanges.render.min);
- vote.refreshRateRanges.render.max = Math.min(vote.refreshRateRanges.physical.max,
- vote.refreshRateRanges.render.max);
- }
- }
-
return votes;
}
@@ -280,6 +264,18 @@ public class DisplayModeDirector {
disableRefreshRateSwitching = false;
appRequestBaseModeRefreshRate = 0f;
}
+
+ @Override
+ public String toString() {
+ return "minPhysicalRefreshRate=" + minPhysicalRefreshRate
+ + ", maxPhysicalRefreshRate=" + maxPhysicalRefreshRate
+ + ", minRenderFrameRate=" + minRenderFrameRate
+ + ", maxRenderFrameRate=" + maxRenderFrameRate
+ + ", width=" + width
+ + ", height=" + height
+ + ", disableRefreshRateSwitching=" + disableRefreshRateSwitching
+ + ", appRequestBaseModeRefreshRate=" + appRequestBaseModeRefreshRate;
+ }
}
// VoteSummary is returned as an output param to cut down a bit on the number of temporary
@@ -333,18 +329,8 @@ public class DisplayModeDirector {
}
if (mLoggingEnabled) {
- Slog.w(TAG, "Vote summary for priority "
- + Vote.priorityToString(priority)
- + ": width=" + summary.width
- + ", height=" + summary.height
- + ", minPhysicalRefreshRate=" + summary.minPhysicalRefreshRate
- + ", maxPhysicalRefreshRate=" + summary.maxPhysicalRefreshRate
- + ", minRenderFrameRate=" + summary.minRenderFrameRate
- + ", maxRenderFrameRate=" + summary.maxRenderFrameRate
- + ", disableRefreshRateSwitching="
- + summary.disableRefreshRateSwitching
- + ", appRequestBaseModeRefreshRate="
- + summary.appRequestBaseModeRefreshRate);
+ Slog.w(TAG, "Vote summary for priority " + Vote.priorityToString(priority)
+ + ": " + summary);
}
}
}
@@ -378,6 +364,23 @@ public class DisplayModeDirector {
return !availableModes.isEmpty() ? availableModes.get(0) : null;
}
+ private void disableModeSwitching(VoteSummary summary, float fps) {
+ summary.minPhysicalRefreshRate = summary.maxPhysicalRefreshRate = fps;
+ summary.maxRenderFrameRate = Math.min(summary.maxRenderFrameRate, fps);
+
+ if (mLoggingEnabled) {
+ Slog.i(TAG, "Disabled mode switching on summary: " + summary);
+ }
+ }
+
+ private void disableRenderRateSwitching(VoteSummary summary) {
+ summary.minRenderFrameRate = summary.maxRenderFrameRate;
+
+ if (mLoggingEnabled) {
+ Slog.i(TAG, "Disabled render rate switching on summary: " + summary);
+ }
+ }
+
/**
* Calculates the refresh rate ranges and display modes that the system is allowed to freely
* switch between based on global and display-specific constraints.
@@ -406,7 +409,7 @@ public class DisplayModeDirector {
int highestConsideredPriority = Vote.MAX_PRIORITY;
if (mAlwaysRespectAppRequest) {
- lowestConsideredPriority = Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE;
+ lowestConsideredPriority = Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE;
highestConsideredPriority = Vote.PRIORITY_APP_REQUEST_SIZE;
}
@@ -534,19 +537,15 @@ public class DisplayModeDirector {
if (modeSwitchingDisabled || primarySummary.disableRefreshRateSwitching) {
float fps = baseMode.getRefreshRate();
- primarySummary.minPhysicalRefreshRate = primarySummary.maxPhysicalRefreshRate = fps;
+ disableModeSwitching(primarySummary, fps);
if (modeSwitchingDisabled) {
- appRequestSummary.minPhysicalRefreshRate =
- appRequestSummary.maxPhysicalRefreshRate = fps;
- }
- }
+ disableModeSwitching(appRequestSummary, fps);
+ disableRenderRateSwitching(primarySummary);
- if (mModeSwitchingType == DisplayManager.SWITCHING_TYPE_NONE
- || mRenderFrameRateIsPhysicalRefreshRate) {
- primarySummary.minRenderFrameRate = primarySummary.minPhysicalRefreshRate;
- primarySummary.maxRenderFrameRate = primarySummary.maxPhysicalRefreshRate;
- appRequestSummary.minRenderFrameRate = appRequestSummary.minPhysicalRefreshRate;
- appRequestSummary.maxRenderFrameRate = appRequestSummary.maxPhysicalRefreshRate;
+ if (mModeSwitchingType == DisplayManager.SWITCHING_TYPE_NONE) {
+ disableRenderRateSwitching(appRequestSummary);
+ }
+ }
}
boolean allowGroupSwitching =
@@ -612,6 +611,22 @@ public class DisplayModeDirector {
continue;
}
+ // The physical refresh rate must be in the render frame rate range, unless
+ // frame rate override is supported.
+ if (!mSupportsFrameRateOverride) {
+ if (physicalRefreshRate < (summary.minRenderFrameRate - FLOAT_TOLERANCE)
+ || physicalRefreshRate > (summary.maxRenderFrameRate + FLOAT_TOLERANCE)) {
+ if (mLoggingEnabled) {
+ Slog.w(TAG, "Discarding mode " + mode.getModeId()
+ + ", outside render rate bounds"
+ + ": minPhysicalRefreshRate=" + summary.minPhysicalRefreshRate
+ + ", maxPhysicalRefreshRate=" + summary.maxPhysicalRefreshRate
+ + ", modeRefreshRate=" + physicalRefreshRate);
+ }
+ continue;
+ }
+ }
+
// Check whether the render frame rate range is achievable by the mode's physical
// refresh rate, meaning that if a divisor of the physical refresh rate is in range
// of the render frame rate.
@@ -2979,7 +2994,7 @@ public class DisplayModeDirector {
IThermalService getThermalService();
- boolean renderFrameRateIsPhysicalRefreshRate();
+ boolean supportsFrameRateOverride();
}
@VisibleForTesting
@@ -3034,9 +3049,11 @@ public class DisplayModeDirector {
}
@Override
- public boolean renderFrameRateIsPhysicalRefreshRate() {
- return DisplayProperties
- .debug_render_frame_rate_is_physical_refresh_rate().orElse(true);
+ public boolean supportsFrameRateOverride() {
+ return SurfaceFlingerProperties.enable_frame_rate_override().orElse(false)
+ && !SurfaceFlingerProperties.frame_rate_override_for_native_rates()
+ .orElse(true)
+ && SurfaceFlingerProperties.frame_rate_override_global().orElse(false);
}
private DisplayManager getDisplayManager() {
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 81245001eaf6..9d478927edd5 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -219,15 +219,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
private final float mScreenBrightnessDefault;
- // The minimum allowed brightness while in VR.
- private final float mScreenBrightnessForVrRangeMinimum;
-
- // The maximum allowed brightness while in VR.
- private final float mScreenBrightnessForVrRangeMaximum;
-
- // The default screen brightness for VR.
- private final float mScreenBrightnessForVrDefault;
-
// True if auto-brightness should be used.
private boolean mUseSoftwareAutoBrightnessConfig;
@@ -450,9 +441,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
// PowerManager.BRIGHTNESS_INVALID_FLOAT when there's no temporary brightness set.
private float mTemporaryScreenBrightness;
- // The current screen brightness while in VR mode.
- private float mScreenBrightnessForVr;
-
// The last auto brightness adjustment that was set by the user and not temporary. Set to
// Float.NaN when an auto-brightness adjustment hasn't been recorded yet.
private float mAutoBrightnessAdjustment;
@@ -563,14 +551,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
mScreenBrightnessDefault = clampAbsoluteBrightness(
mLogicalDisplay.getDisplayInfoLocked().brightnessDefault);
- // VR SETTINGS
- mScreenBrightnessForVrDefault = clampAbsoluteBrightness(
- pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DEFAULT_VR));
- mScreenBrightnessForVrRangeMaximum = clampAbsoluteBrightness(
- pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM_VR));
- mScreenBrightnessForVrRangeMinimum = clampAbsoluteBrightness(
- pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM_VR));
-
mAllowAutoBrightnessWhileDozingConfig = resources.getBoolean(
com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing);
@@ -643,7 +623,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
loadProximitySensor();
mCurrentScreenBrightnessSetting = getScreenBrightnessSetting();
- mScreenBrightnessForVr = getScreenBrightnessForVrSetting();
mAutoBrightnessAdjustment = getAutoBrightnessAdjustmentSetting();
mTemporaryScreenBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
mPendingScreenBrightnessSetting = PowerManager.BRIGHTNESS_INVALID_FLOAT;
@@ -949,9 +928,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
mBrightnessSetting.registerListener(mBrightnessSettingListener);
mContext.getContentResolver().registerContentObserver(
- Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_FOR_VR_FLOAT),
- false /*notifyForDescendants*/, mSettingsObserver, UserHandle.USER_ALL);
- mContext.getContentResolver().registerContentObserver(
Settings.System.getUriFor(Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ),
false /*notifyForDescendants*/, mSettingsObserver, UserHandle.USER_ALL);
}
@@ -1271,9 +1247,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
mBrightnessReasonTemp.setReason(BrightnessReason.REASON_DOZE);
}
break;
- case DisplayPowerRequest.POLICY_VR:
- state = Display.STATE_VR;
- break;
case DisplayPowerRequest.POLICY_DIM:
case DisplayPowerRequest.POLICY_BRIGHT:
default:
@@ -1351,12 +1324,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
mBrightnessReasonTemp.setReason(BrightnessReason.REASON_SCREEN_OFF);
}
- // Always use the VR brightness when in the VR state.
- if (state == Display.STATE_VR) {
- brightnessState = mScreenBrightnessForVr;
- mBrightnessReasonTemp.setReason(BrightnessReason.REASON_VR);
- }
-
if ((Float.isNaN(brightnessState))
&& isValidBrightnessValue(mPowerRequest.screenBrightnessOverride)) {
brightnessState = mPowerRequest.screenBrightnessOverride;
@@ -1575,7 +1542,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
mBrightnessThrottler.getBrightnessMaxReason());
// Animate the screen brightness when the screen is on or dozing.
- // Skip the animation when the screen is off or suspended or transition to/from VR.
+ // Skip the animation when the screen is off.
boolean brightnessAdjusted = false;
final boolean brightnessIsTemporary =
mAppliedTemporaryBrightness || mAppliedTemporaryAutoBrightnessAdjustment;
@@ -1598,8 +1565,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
}
}
- final boolean wasOrWillBeInVr =
- (state == Display.STATE_VR || oldState == Display.STATE_VR);
final boolean initialRampSkip = (state == Display.STATE_ON && mSkipRampState
!= RAMP_STATE_SKIP_NONE) || skipRampBecauseOfProximityChangeToNegative;
// While dozing, sometimes the brightness is split into buckets. Rather than animating
@@ -1641,7 +1606,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
&& (animateValue != currentBrightness
|| sdrAnimateValue != currentSdrBrightness)) {
if (initialRampSkip || hasBrightnessBuckets
- || wasOrWillBeInVr || !isDisplayContentVisible || brightnessIsTemporary) {
+ || !isDisplayContentVisible || brightnessIsTemporary) {
animateScreenBrightness(animateValue, sdrAnimateValue,
SCREEN_ANIMATION_RATE_MINIMUM);
} else {
@@ -2078,12 +2043,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
}
}
- private float clampScreenBrightnessForVr(float value) {
- return MathUtils.constrain(
- value, mScreenBrightnessForVrRangeMinimum,
- mScreenBrightnessForVrRangeMaximum);
- }
-
private float clampScreenBrightness(float value) {
if (Float.isNaN(value)) {
value = PowerManager.BRIGHTNESS_MIN;
@@ -2172,23 +2131,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
mPowerState.setColorFadeLevel(1.0f);
mPowerState.dismissColorFade();
}
- } else if (target == Display.STATE_VR) {
- // Wait for brightness animation to complete beforehand when entering VR
- // from screen on to prevent a perceptible jump because brightness may operate
- // differently when the display is configured for dozing.
- if (mScreenBrightnessRampAnimator.isAnimating()
- && mPowerState.getScreenState() == Display.STATE_ON) {
- return;
- }
-
- // Set screen state.
- if (!setScreenState(Display.STATE_VR)) {
- return; // screen on blocked
- }
-
- // Dismiss the black surface without fanfare.
- mPowerState.setColorFadeLevel(1.0f);
- mPowerState.dismissColorFade();
} else if (target == Display.STATE_DOZE) {
// Want screen dozing.
// Wait for brightness animation to complete beforehand when entering doze
@@ -2396,9 +2338,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
mAutomaticBrightnessController.resetShortTermModel();
}
}
- // We don't bother with a pending variable for VR screen brightness since we just
- // immediately adapt to it.
- mScreenBrightnessForVr = getScreenBrightnessForVrSetting();
sendUpdatePowerState();
}
@@ -2417,13 +2356,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
return clampAbsoluteBrightness(brightness);
}
- private float getScreenBrightnessForVrSetting() {
- final float brightnessFloat = Settings.System.getFloatForUser(mContext.getContentResolver(),
- Settings.System.SCREEN_BRIGHTNESS_FOR_VR_FLOAT, mScreenBrightnessForVrDefault,
- UserHandle.USER_CURRENT);
- return clampScreenBrightnessForVr(brightnessFloat);
- }
-
@Override
public void setBrightness(float brightnessValue) {
// Update the setting, which will eventually call back into DPC to have us actually update
@@ -2503,7 +2435,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
: 1.0f;
mBrightnessTracker.notifyBrightnessChanged(brightnessInNits, userInitiated,
powerFactor, hadUserDataPoint,
- mAutomaticBrightnessController.isDefaultConfig(), mUniqueDisplayId);
+ mAutomaticBrightnessController.isDefaultConfig(), mUniqueDisplayId,
+ mAutomaticBrightnessController.getLastSensorValues(),
+ mAutomaticBrightnessController.getLastSensorTimestamps());
}
}
@@ -2597,9 +2531,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
pw.println(" mScreenBrightnessRangeDefault=" + mScreenBrightnessDefault);
pw.println(" mScreenBrightnessDozeConfig=" + mScreenBrightnessDozeConfig);
pw.println(" mScreenBrightnessDimConfig=" + mScreenBrightnessDimConfig);
- pw.println(" mScreenBrightnessForVrRangeMinimum=" + mScreenBrightnessForVrRangeMinimum);
- pw.println(" mScreenBrightnessForVrRangeMaximum=" + mScreenBrightnessForVrRangeMaximum);
- pw.println(" mScreenBrightnessForVrDefault=" + mScreenBrightnessForVrDefault);
pw.println(" mUseSoftwareAutoBrightnessConfig=" + mUseSoftwareAutoBrightnessConfig);
pw.println(" mAllowAutoBrightnessWhileDozingConfig="
+ mAllowAutoBrightnessWhileDozingConfig);
@@ -2649,7 +2580,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
pw.println(" mBrightnessReason=" + mBrightnessReason);
pw.println(" mTemporaryAutoBrightnessAdjustment=" + mTemporaryAutoBrightnessAdjustment);
pw.println(" mPendingAutoBrightnessAdjustment=" + mPendingAutoBrightnessAdjustment);
- pw.println(" mScreenBrightnessForVrFloat=" + mScreenBrightnessForVr);
pw.println(" mAppliedAutoBrightness=" + mAppliedAutoBrightness);
pw.println(" mAppliedDimming=" + mAppliedDimming);
pw.println(" mAppliedLowPower=" + mAppliedLowPower);
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index 9a594e8e059e..346b340edcd1 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -198,15 +198,6 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
private final float mScreenBrightnessDefault;
- // The minimum allowed brightness while in VR.
- private final float mScreenBrightnessForVrRangeMinimum;
-
- // The maximum allowed brightness while in VR.
- private final float mScreenBrightnessForVrRangeMaximum;
-
- // The default screen brightness for VR.
- private final float mScreenBrightnessForVrDefault;
-
// True if auto-brightness should be used.
private boolean mUseSoftwareAutoBrightnessConfig;
@@ -300,7 +291,6 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
private boolean mAppliedDimming;
private boolean mAppliedLowPower;
private boolean mAppliedTemporaryAutoBrightnessAdjustment;
- private boolean mAppliedBrightnessBoost;
private boolean mAppliedThrottling;
// Reason for which the brightness was last changed. See {@link BrightnessReason} for more
@@ -394,9 +384,6 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
// behalf of the user.
private float mCurrentScreenBrightnessSetting;
- // The current screen brightness while in VR mode.
- private float mScreenBrightnessForVr;
-
// The last auto brightness adjustment that was set by the user and not temporary. Set to
// Float.NaN when an auto-brightness adjustment hasn't been recorded yet.
private float mAutoBrightnessAdjustment;
@@ -488,14 +475,6 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
mScreenBrightnessDefault = clampAbsoluteBrightness(
mLogicalDisplay.getDisplayInfoLocked().brightnessDefault);
- // VR SETTINGS
- mScreenBrightnessForVrDefault = clampAbsoluteBrightness(
- pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DEFAULT_VR));
- mScreenBrightnessForVrRangeMaximum = clampAbsoluteBrightness(
- pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM_VR));
- mScreenBrightnessForVrRangeMinimum = clampAbsoluteBrightness(
- pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM_VR));
-
loadBrightnessRampRates();
mSkipScreenOnBrightnessRamp = resources.getBoolean(
R.bool.config_skipScreenOnBrightnessRamp);
@@ -562,7 +541,6 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
mDisplayBrightnessController =
new DisplayBrightnessController(context, null, mDisplayId);
mCurrentScreenBrightnessSetting = getScreenBrightnessSetting();
- mScreenBrightnessForVr = getScreenBrightnessForVrSetting();
mAutoBrightnessAdjustment = getAutoBrightnessAdjustmentSetting();
mPendingScreenBrightnessSetting = PowerManager.BRIGHTNESS_INVALID_FLOAT;
mTemporaryAutoBrightnessAdjustment = PowerManager.BRIGHTNESS_INVALID_FLOAT;
@@ -862,9 +840,6 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
mBrightnessSetting.registerListener(mBrightnessSettingListener);
mContext.getContentResolver().registerContentObserver(
- Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_FOR_VR_FLOAT),
- false /*notifyForDescendants*/, mSettingsObserver, UserHandle.USER_ALL);
- mContext.getContentResolver().registerContentObserver(
Settings.System.getUriFor(Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ),
false /*notifyForDescendants*/, mSettingsObserver, UserHandle.USER_ALL);
}
@@ -1163,9 +1138,6 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
state = Display.STATE_DOZE;
}
break;
- case DisplayPowerRequest.POLICY_VR:
- state = Display.STATE_VR;
- break;
case DisplayPowerRequest.POLICY_DIM:
case DisplayPowerRequest.POLICY_BRIGHT:
default:
@@ -1199,12 +1171,6 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
float brightnessState = displayBrightnessState.getBrightness();
mBrightnessReasonTemp.set(displayBrightnessState.getBrightnessReason());
- // Always use the VR brightness when in the VR state.
- if (state == Display.STATE_VR) {
- brightnessState = mScreenBrightnessForVr;
- mBrightnessReasonTemp.setReason(BrightnessReason.REASON_VR);
- }
-
final boolean autoBrightnessEnabledInDoze =
mDisplayBrightnessController.isAllowAutoBrightnessWhileDozingConfig()
&& Display.isDozeState(state);
@@ -1235,18 +1201,6 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
brightnessAdjustmentFlags = BrightnessReason.ADJUSTMENT_AUTO;
mAppliedTemporaryAutoBrightnessAdjustment = false;
}
- // Apply brightness boost.
- // We do this here after deciding whether auto-brightness is enabled so that we don't
- // disable the light sensor during this temporary state. That way when boost ends we will
- // be able to resume normal auto-brightness behavior without any delay.
- if (mPowerRequest.boostScreenBrightness
- && brightnessState != PowerManager.BRIGHTNESS_OFF_FLOAT) {
- brightnessState = PowerManager.BRIGHTNESS_MAX;
- mBrightnessReasonTemp.setReason(BrightnessReason.REASON_BOOST);
- mAppliedBrightnessBoost = true;
- } else {
- mAppliedBrightnessBoost = false;
- }
// If the brightness is already set then it's been overridden by something other than the
// user, or is a temporary adjustment.
@@ -1405,7 +1359,7 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
mBrightnessThrottler.getBrightnessMaxReason());
// Animate the screen brightness when the screen is on or dozing.
- // Skip the animation when the screen is off or suspended or transition to/from VR.
+ // Skip the animation when the screen is off or suspended.
boolean brightnessAdjusted = false;
final boolean brightnessIsTemporary =
(mBrightnessReason.getReason() == BrightnessReason.REASON_TEMPORARY)
@@ -1429,8 +1383,6 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
}
}
- final boolean wasOrWillBeInVr =
- (state == Display.STATE_VR || oldState == Display.STATE_VR);
final boolean initialRampSkip = (state == Display.STATE_ON && mSkipRampState
!= RAMP_STATE_SKIP_NONE) || mDisplayPowerProximityStateController
.shouldSkipRampBecauseOfProximityChangeToNegative();
@@ -1473,7 +1425,7 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
&& (animateValue != currentBrightness
|| sdrAnimateValue != currentSdrBrightness)) {
if (initialRampSkip || hasBrightnessBuckets
- || wasOrWillBeInVr || !isDisplayContentVisible || brightnessIsTemporary) {
+ || !isDisplayContentVisible || brightnessIsTemporary) {
animateScreenBrightness(animateValue, sdrAnimateValue,
SCREEN_ANIMATION_RATE_MINIMUM);
} else {
@@ -1886,12 +1838,6 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
fallbackType);
}
- private float clampScreenBrightnessForVr(float value) {
- return MathUtils.constrain(
- value, mScreenBrightnessForVrRangeMinimum,
- mScreenBrightnessForVrRangeMaximum);
- }
-
private float clampScreenBrightness(float value) {
if (Float.isNaN(value)) {
value = PowerManager.BRIGHTNESS_MIN;
@@ -1980,23 +1926,6 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
mPowerState.setColorFadeLevel(1.0f);
mPowerState.dismissColorFade();
}
- } else if (target == Display.STATE_VR) {
- // Wait for brightness animation to complete beforehand when entering VR
- // from screen on to prevent a perceptible jump because brightness may operate
- // differently when the display is configured for dozing.
- if (mScreenBrightnessRampAnimator.isAnimating()
- && mPowerState.getScreenState() == Display.STATE_ON) {
- return;
- }
-
- // Set screen state.
- if (!setScreenState(Display.STATE_VR)) {
- return; // screen on blocked
- }
-
- // Dismiss the black surface without fanfare.
- mPowerState.setColorFadeLevel(1.0f);
- mPowerState.dismissColorFade();
} else if (target == Display.STATE_DOZE) {
// Want screen dozing.
// Wait for brightness animation to complete beforehand when entering doze
@@ -2113,9 +2042,6 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
mAutomaticBrightnessController.resetShortTermModel();
}
}
- // We don't bother with a pending variable for VR screen brightness since we just
- // immediately adapt to it.
- mScreenBrightnessForVr = getScreenBrightnessForVrSetting();
sendUpdatePowerState();
}
@@ -2134,13 +2060,6 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
return clampAbsoluteBrightness(brightness);
}
- private float getScreenBrightnessForVrSetting() {
- final float brightnessFloat = Settings.System.getFloatForUser(mContext.getContentResolver(),
- Settings.System.SCREEN_BRIGHTNESS_FOR_VR_FLOAT, mScreenBrightnessForVrDefault,
- UserHandle.USER_CURRENT);
- return clampScreenBrightnessForVr(brightnessFloat);
- }
-
@Override
public void setBrightness(float brightnessValue) {
// Update the setting, which will eventually call back into DPC to have us actually update
@@ -2222,7 +2141,9 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
: 1.0f;
mBrightnessTracker.notifyBrightnessChanged(brightnessInNits, userInitiated,
powerFactor, hadUserDataPoint,
- mAutomaticBrightnessController.isDefaultConfig(), mUniqueDisplayId);
+ mAutomaticBrightnessController.isDefaultConfig(), mUniqueDisplayId,
+ mAutomaticBrightnessController.getLastSensorValues(),
+ mAutomaticBrightnessController.getLastSensorTimestamps());
}
}
@@ -2254,9 +2175,6 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
pw.println(" mScreenBrightnessRangeDefault=" + mScreenBrightnessDefault);
pw.println(" mScreenBrightnessDozeConfig=" + mScreenBrightnessDozeConfig);
pw.println(" mScreenBrightnessDimConfig=" + mScreenBrightnessDimConfig);
- pw.println(" mScreenBrightnessForVrRangeMinimum=" + mScreenBrightnessForVrRangeMinimum);
- pw.println(" mScreenBrightnessForVrRangeMaximum=" + mScreenBrightnessForVrRangeMaximum);
- pw.println(" mScreenBrightnessForVrDefault=" + mScreenBrightnessForVrDefault);
pw.println(" mUseSoftwareAutoBrightnessConfig=" + mUseSoftwareAutoBrightnessConfig);
pw.println(" mSkipScreenOnBrightnessRamp=" + mSkipScreenOnBrightnessRamp);
pw.println(" mColorFadeFadesConfig=" + mColorFadeFadesConfig);
@@ -2292,14 +2210,12 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
pw.println(" mBrightnessReason=" + mBrightnessReason);
pw.println(" mTemporaryAutoBrightnessAdjustment=" + mTemporaryAutoBrightnessAdjustment);
pw.println(" mPendingAutoBrightnessAdjustment=" + mPendingAutoBrightnessAdjustment);
- pw.println(" mScreenBrightnessForVrFloat=" + mScreenBrightnessForVr);
pw.println(" mAppliedAutoBrightness=" + mAppliedAutoBrightness);
pw.println(" mAppliedDimming=" + mAppliedDimming);
pw.println(" mAppliedLowPower=" + mAppliedLowPower);
pw.println(" mAppliedThrottling=" + mAppliedThrottling);
pw.println(" mAppliedTemporaryAutoBrightnessAdjustment="
+ mAppliedTemporaryAutoBrightnessAdjustment);
- pw.println(" mAppliedBrightnessBoost=" + mAppliedBrightnessBoost);
pw.println(" mDozing=" + mDozing);
pw.println(" mSkipRampState=" + skipRampStateToString(mSkipRampState));
pw.println(" mScreenOnBlockStartRealTime=" + mScreenOnBlockStartRealTime);
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 4bf1e98f99a5..be5980bdfffd 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -112,13 +112,13 @@ final class LocalDisplayAdapter extends DisplayAdapter {
mSurfaceControlProxy.getPhysicalDisplayToken(physicalDisplayId);
if (displayToken != null) {
SurfaceControl.StaticDisplayInfo staticInfo =
- mSurfaceControlProxy.getStaticDisplayInfo(displayToken);
+ mSurfaceControlProxy.getStaticDisplayInfo(physicalDisplayId);
if (staticInfo == null) {
Slog.w(TAG, "No valid static info found for display device " + physicalDisplayId);
return;
}
SurfaceControl.DynamicDisplayInfo dynamicInfo =
- mSurfaceControlProxy.getDynamicDisplayInfo(displayToken);
+ mSurfaceControlProxy.getDynamicDisplayInfo(physicalDisplayId);
if (dynamicInfo == null) {
Slog.w(TAG, "No valid dynamic info found for display device " + physicalDisplayId);
return;
@@ -226,6 +226,8 @@ final class LocalDisplayAdapter extends DisplayAdapter {
private SurfaceControl.DisplayMode[] mSfDisplayModes;
// The active display mode in SurfaceFlinger
private SurfaceControl.DisplayMode mActiveSfDisplayMode;
+ // The active display vsync period in SurfaceFlinger
+ private float mActiveRenderFrameRate;
private DisplayEventReceiver.FrameRateOverride[] mFrameRateOverrides =
new DisplayEventReceiver.FrameRateOverride[0];
@@ -267,7 +269,7 @@ final class LocalDisplayAdapter extends DisplayAdapter {
SurfaceControl.DesiredDisplayModeSpecs modeSpecs) {
boolean changed = updateDisplayModesLocked(
dynamicInfo.supportedDisplayModes, dynamicInfo.preferredBootDisplayMode,
- dynamicInfo.activeDisplayModeId, modeSpecs);
+ dynamicInfo.activeDisplayModeId, dynamicInfo.renderFrameRate, modeSpecs);
changed |= updateStaticInfo(staticInfo);
changed |= updateColorModesLocked(dynamicInfo.supportedColorModes,
dynamicInfo.activeColorMode);
@@ -283,7 +285,8 @@ final class LocalDisplayAdapter extends DisplayAdapter {
public boolean updateDisplayModesLocked(
SurfaceControl.DisplayMode[] displayModes, int preferredSfDisplayModeId,
- int activeSfDisplayModeId, SurfaceControl.DesiredDisplayModeSpecs modeSpecs) {
+ int activeSfDisplayModeId, float renderFrameRate,
+ SurfaceControl.DesiredDisplayModeSpecs modeSpecs) {
mSfDisplayModes = Arrays.copyOf(displayModes, displayModes.length);
mActiveSfDisplayMode = getModeById(displayModes, activeSfDisplayModeId);
SurfaceControl.DisplayMode preferredSfDisplayMode =
@@ -379,6 +382,16 @@ final class LocalDisplayAdapter extends DisplayAdapter {
sendTraversalRequestLocked();
}
+ boolean renderFrameRateChanged = false;
+
+ if (mActiveRenderFrameRate > 0 && mActiveRenderFrameRate != renderFrameRate) {
+ Slog.d(TAG, "The render frame rate was changed from SurfaceFlinger or the display"
+ + " device to " + renderFrameRate);
+ mActiveRenderFrameRate = renderFrameRate;
+ renderFrameRateChanged = true;
+ sendTraversalRequestLocked();
+ }
+
// Check whether surface flinger spontaneously changed display config specs out from
// under us. If so, schedule a traversal to reapply our display config specs.
if (mDisplayModeSpecs.baseModeId != INVALID_MODE_ID) {
@@ -398,7 +411,7 @@ final class LocalDisplayAdapter extends DisplayAdapter {
boolean recordsChanged = records.size() != mSupportedModes.size() || modesAdded;
// If the records haven't changed then we're done here.
if (!recordsChanged) {
- return activeModeChanged || preferredModeChanged;
+ return activeModeChanged || preferredModeChanged || renderFrameRateChanged;
}
mSupportedModes.clear();
@@ -410,16 +423,19 @@ final class LocalDisplayAdapter extends DisplayAdapter {
if (mDefaultModeId == INVALID_MODE_ID) {
mDefaultModeId = activeRecord.mMode.getModeId();
mDefaultModeGroup = mActiveSfDisplayMode.group;
+ mActiveRenderFrameRate = renderFrameRate;
} else if (modesAdded && activeModeChanged) {
Slog.d(TAG, "New display modes are added and the active mode has changed, "
+ "use active mode as default mode.");
mDefaultModeId = activeRecord.mMode.getModeId();
mDefaultModeGroup = mActiveSfDisplayMode.group;
+ mActiveRenderFrameRate = renderFrameRate;
} else if (findSfDisplayModeIdLocked(mDefaultModeId, mDefaultModeGroup) < 0) {
Slog.w(TAG, "Default display mode no longer available, using currently"
+ " active mode as default.");
mDefaultModeId = activeRecord.mMode.getModeId();
mDefaultModeGroup = mActiveSfDisplayMode.group;
+ mActiveRenderFrameRate = renderFrameRate;
}
// Determine whether the display mode specs' base mode is still there.
@@ -584,7 +600,9 @@ final class LocalDisplayAdapter extends DisplayAdapter {
DisplayModeRecord record = mSupportedModes.valueAt(i);
if (record.hasMatchingMode(mode)
&& refreshRatesEquals(alternativeRefreshRates,
- record.mMode.getAlternativeRefreshRates())) {
+ record.mMode.getAlternativeRefreshRates())
+ && hdrTypesEqual(mode.supportedHdrTypes,
+ record.mMode.getSupportedHdrTypes())) {
return record;
}
}
@@ -618,6 +636,7 @@ final class LocalDisplayAdapter extends DisplayAdapter {
mInfo.width = mActiveSfDisplayMode.width;
mInfo.height = mActiveSfDisplayMode.height;
mInfo.modeId = mActiveModeId;
+ mInfo.renderFrameRate = mActiveRenderFrameRate;
mInfo.defaultModeId = getPreferredModeId();
mInfo.supportedModes = getDisplayModes(mSupportedModes);
mInfo.colorMode = mActiveColorMode;
@@ -762,18 +781,8 @@ final class LocalDisplayAdapter extends DisplayAdapter {
}
}
- // If the state change was from or to VR, then we need to tell the light
- // so that it can apply appropriate VR brightness settings. Also, update the
- // brightness so the state is propogated to light.
- boolean vrModeChange = false;
- if ((state == Display.STATE_VR || currentState == Display.STATE_VR) &&
- currentState != state) {
- setVrMode(state == Display.STATE_VR);
- vrModeChange = true;
- }
-
// Apply brightness changes given that we are in a non-suspended state.
- if (brightnessChanged || vrModeChange) {
+ if (brightnessChanged) {
setDisplayBrightness(brightnessState, sdrBrightnessState);
mBrightnessState = brightnessState;
mSdrBrightnessState = sdrBrightnessState;
@@ -785,15 +794,6 @@ final class LocalDisplayAdapter extends DisplayAdapter {
}
}
- private void setVrMode(boolean isVrEnabled) {
- if (DEBUG) {
- Slog.d(TAG, "setVrMode("
- + "id=" + physicalDisplayId
- + ", state=" + Display.stateToString(state) + ")");
- }
- mBacklightAdapter.setVrMode(isVrEnabled);
- }
-
private void setDisplayState(int state) {
if (DEBUG) {
Slog.d(TAG, "setDisplayState("
@@ -1012,8 +1012,8 @@ final class LocalDisplayAdapter extends DisplayAdapter {
updateDeviceInfoLocked();
}
- public void onActiveDisplayModeChangedLocked(int sfModeId) {
- if (updateActiveModeLocked(sfModeId)) {
+ public void onActiveDisplayModeChangedLocked(int sfModeId, float renderFrameRate) {
+ if (updateActiveModeLocked(sfModeId, renderFrameRate)) {
updateDeviceInfoLocked();
}
}
@@ -1025,8 +1025,9 @@ final class LocalDisplayAdapter extends DisplayAdapter {
}
}
- public boolean updateActiveModeLocked(int activeSfModeId) {
- if (mActiveSfDisplayMode.id == activeSfModeId) {
+ public boolean updateActiveModeLocked(int activeSfModeId, float renderFrameRate) {
+ if (mActiveSfDisplayMode.id == activeSfModeId
+ && mActiveRenderFrameRate == renderFrameRate) {
return false;
}
mActiveSfDisplayMode = getModeById(mSfDisplayModes, activeSfModeId);
@@ -1035,6 +1036,7 @@ final class LocalDisplayAdapter extends DisplayAdapter {
Slog.w(TAG, "In unknown mode after setting allowed modes"
+ ", activeModeId=" + activeSfModeId);
}
+ mActiveRenderFrameRate = renderFrameRate;
return true;
}
@@ -1131,6 +1133,7 @@ final class LocalDisplayAdapter extends DisplayAdapter {
pw.println(" " + sfDisplayMode);
}
pw.println("mActiveSfDisplayMode=" + mActiveSfDisplayMode);
+ pw.println("mActiveRenderFrameRate=" + mActiveRenderFrameRate);
pw.println("mSupportedModes=");
for (int i = 0; i < mSupportedModes.size(); i++) {
pw.println(" " + mSupportedModes.valueAt(i));
@@ -1245,6 +1248,13 @@ final class LocalDisplayAdapter extends DisplayAdapter {
}
}
+ private boolean hdrTypesEqual(int[] modeHdrTypes, int[] recordHdrTypes) {
+ int[] modeHdrTypesCopy = Arrays.copyOf(modeHdrTypes, modeHdrTypes.length);
+ Arrays.sort(modeHdrTypesCopy);
+ // Record HDR types are already sorted when we create the DisplayModeRecord
+ return Arrays.equals(modeHdrTypesCopy, recordHdrTypes);
+ }
+
/** Supplies a context whose Resources apply runtime-overlays */
Context getOverlayContext() {
if (mOverlayContext == null) {
@@ -1262,7 +1272,7 @@ final class LocalDisplayAdapter extends DisplayAdapter {
DisplayModeRecord(SurfaceControl.DisplayMode mode,
float[] alternativeRefreshRates) {
mMode = createMode(mode.width, mode.height, mode.refreshRate,
- alternativeRefreshRates);
+ alternativeRefreshRates, mode.supportedHdrTypes);
}
/**
@@ -1276,7 +1286,7 @@ final class LocalDisplayAdapter extends DisplayAdapter {
return mMode.getPhysicalWidth() == mode.width
&& mMode.getPhysicalHeight() == mode.height
&& Float.floatToIntBits(mMode.getRefreshRate())
- == Float.floatToIntBits(mode.refreshRate);
+ == Float.floatToIntBits(mode.refreshRate);
}
public String toString() {
@@ -1298,7 +1308,8 @@ final class LocalDisplayAdapter extends DisplayAdapter {
public interface DisplayEventListener {
void onHotplug(long timestampNanos, long physicalDisplayId, boolean connected);
- void onModeChanged(long timestampNanos, long physicalDisplayId, int modeId);
+ void onModeChanged(long timestampNanos, long physicalDisplayId, int modeId,
+ long renderPeriod);
void onFrameRateOverridesChanged(long timestampNanos, long physicalDisplayId,
DisplayEventReceiver.FrameRateOverride[] overrides);
@@ -1319,8 +1330,9 @@ final class LocalDisplayAdapter extends DisplayAdapter {
}
@Override
- public void onModeChanged(long timestampNanos, long physicalDisplayId, int modeId) {
- mListener.onModeChanged(timestampNanos, physicalDisplayId, modeId);
+ public void onModeChanged(long timestampNanos, long physicalDisplayId, int modeId,
+ long renderPeriod) {
+ mListener.onModeChanged(timestampNanos, physicalDisplayId, modeId, renderPeriod);
}
@Override
@@ -1343,12 +1355,14 @@ final class LocalDisplayAdapter extends DisplayAdapter {
}
@Override
- public void onModeChanged(long timestampNanos, long physicalDisplayId, int modeId) {
+ public void onModeChanged(long timestampNanos, long physicalDisplayId, int modeId,
+ long renderPeriod) {
if (DEBUG) {
Slog.d(TAG, "onModeChanged("
+ "timestampNanos=" + timestampNanos
+ ", physicalDisplayId=" + physicalDisplayId
- + ", modeId=" + modeId + ")");
+ + ", modeId=" + modeId
+ + ", renderPeriod=" + renderPeriod + ")");
}
synchronized (getSyncRoot()) {
LocalDisplayDevice device = mDevices.get(physicalDisplayId);
@@ -1359,7 +1373,8 @@ final class LocalDisplayAdapter extends DisplayAdapter {
}
return;
}
- device.onActiveDisplayModeChangedLocked(modeId);
+ float renderFrameRate = 1e9f / renderPeriod;
+ device.onActiveDisplayModeChangedLocked(modeId, renderFrameRate);
}
}
@@ -1387,8 +1402,8 @@ final class LocalDisplayAdapter extends DisplayAdapter {
@VisibleForTesting
public static class SurfaceControlProxy {
- public SurfaceControl.DynamicDisplayInfo getDynamicDisplayInfo(IBinder token) {
- return SurfaceControl.getDynamicDisplayInfo(token);
+ public SurfaceControl.DynamicDisplayInfo getDynamicDisplayInfo(long displayId) {
+ return SurfaceControl.getDynamicDisplayInfo(displayId);
}
public long[] getPhysicalDisplayIds() {
@@ -1399,8 +1414,8 @@ final class LocalDisplayAdapter extends DisplayAdapter {
return DisplayControl.getPhysicalDisplayToken(physicalDisplayId);
}
- public SurfaceControl.StaticDisplayInfo getStaticDisplayInfo(IBinder displayToken) {
- return SurfaceControl.getStaticDisplayInfo(displayToken);
+ public SurfaceControl.StaticDisplayInfo getStaticDisplayInfo(long displayId) {
+ return SurfaceControl.getStaticDisplayInfo(displayId);
}
public SurfaceControl.DesiredDisplayModeSpecs getDesiredDisplayModeSpecs(
@@ -1505,12 +1520,6 @@ final class LocalDisplayAdapter extends DisplayAdapter {
}
}
- void setVrMode(boolean isVrModeEnabled) {
- if (mBacklight != null) {
- mBacklight.setVrMode(isVrModeEnabled);
- }
- }
-
void setForceSurfaceControl(boolean forceSurfaceControl) {
mForceSurfaceControl = forceSurfaceControl;
}
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index 8dd169bf4bf6..c7b27deb420d 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -377,6 +377,7 @@ final class LogicalDisplay {
mBaseDisplayInfo.logicalHeight = maskedHeight;
mBaseDisplayInfo.rotation = Surface.ROTATION_0;
mBaseDisplayInfo.modeId = deviceInfo.modeId;
+ mBaseDisplayInfo.renderFrameRate = deviceInfo.renderFrameRate;
mBaseDisplayInfo.defaultModeId = deviceInfo.defaultModeId;
mBaseDisplayInfo.supportedModes = Arrays.copyOf(
deviceInfo.supportedModes, deviceInfo.supportedModes.length);
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index 66073c2abdec..80f47a138d08 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -40,6 +40,7 @@ import android.view.DisplayAddress;
import android.view.DisplayInfo;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.display.layout.DisplayIdProducer;
import com.android.server.display.layout.Layout;
import java.io.PrintWriter;
@@ -82,6 +83,8 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
private static final int UPDATE_STATE_TRANSITION = 1;
private static final int UPDATE_STATE_UPDATED = 2;
+ private static int sNextNonDefaultDisplayId = DEFAULT_DISPLAY + 1;
+
/**
* Temporary display info, used for comparing display configurations.
*/
@@ -170,6 +173,8 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
private final ArrayMap<String, Integer> mVirtualDeviceDisplayMapping = new ArrayMap<>();
private int mNextNonDefaultGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
+ private final DisplayIdProducer mIdProducer = (isDefault) ->
+ isDefault ? DEFAULT_DISPLAY : sNextNonDefaultDisplayId++;
private Layout mCurrentLayout = null;
private int mDeviceState = DeviceStateManager.INVALID_DEVICE_STATE;
private int mPendingDeviceState = DeviceStateManager.INVALID_DEVICE_STATE;
@@ -179,7 +184,9 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
LogicalDisplayMapper(@NonNull Context context, @NonNull DisplayDeviceRepository repo,
@NonNull Listener listener, @NonNull DisplayManagerService.SyncRoot syncRoot,
@NonNull Handler handler) {
- this(context, repo, listener, syncRoot, handler, new DeviceStateToLayoutMap());
+ this(context, repo, listener, syncRoot, handler,
+ new DeviceStateToLayoutMap((isDefault) -> isDefault ? DEFAULT_DISPLAY
+ : sNextNonDefaultDisplayId++));
}
LogicalDisplayMapper(@NonNull Context context, @NonNull DisplayDeviceRepository repo,
@@ -588,7 +595,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
// Create a logical display for the new display device
LogicalDisplay display = createNewLogicalDisplayLocked(
- device, Layout.assignDisplayIdLocked(false /*isDefault*/));
+ device, mIdProducer.getId(/* isDefault= */ false));
applyLayoutLocked();
updateLogicalDisplaysLocked();
@@ -621,7 +628,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
& DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY) != 0
&& !nextDeviceInfo.address.equals(deviceInfo.address)) {
layout.createDisplayLocked(nextDeviceInfo.address,
- /* isDefault= */ true, /* isEnabled= */ true);
+ /* isDefault= */ true, /* isEnabled= */ true, mIdProducer);
applyLayoutLocked();
return;
}
@@ -1036,7 +1043,8 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
return;
}
final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
- layout.createDisplayLocked(info.address, /* isDefault= */ true, /* isEnabled= */ true);
+ layout.createDisplayLocked(info.address, /* isDefault= */ true, /* isEnabled= */ true,
+ mIdProducer);
}
private int assignLayerStackLocked(int displayId) {
diff --git a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
index 0e11b53e0257..3e67f0a466d7 100644
--- a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
@@ -340,6 +340,7 @@ final class OverlayDisplayAdapter extends DisplayAdapter {
mInfo.width = mode.getPhysicalWidth();
mInfo.height = mode.getPhysicalHeight();
mInfo.modeId = mode.getModeId();
+ mInfo.renderFrameRate = mode.getRefreshRate();
mInfo.defaultModeId = mModes[0].getModeId();
mInfo.supportedModes = mModes;
mInfo.densityDpi = rawMode.mDensityDpi;
diff --git a/services/core/java/com/android/server/display/PersistentDataStore.java b/services/core/java/com/android/server/display/PersistentDataStore.java
index f30a84fc62e5..e7601bcd8f3a 100644
--- a/services/core/java/com/android/server/display/PersistentDataStore.java
+++ b/services/core/java/com/android/server/display/PersistentDataStore.java
@@ -619,7 +619,7 @@ final class PersistentDataStore {
private static final class DisplayState {
private int mColorMode;
- private float mBrightness;
+ private float mBrightness = Float.NaN;
private int mWidth;
private int mHeight;
private float mRefreshRate;
@@ -700,7 +700,11 @@ final class PersistentDataStore {
break;
case TAG_BRIGHTNESS_VALUE:
String brightness = parser.nextText();
- mBrightness = Float.parseFloat(brightness);
+ try {
+ mBrightness = Float.parseFloat(brightness);
+ } catch (NumberFormatException e) {
+ mBrightness = Float.NaN;
+ }
break;
case TAG_BRIGHTNESS_CONFIGURATIONS:
mDisplayBrightnessConfigurations.loadFromXml(parser);
@@ -727,7 +731,9 @@ final class PersistentDataStore {
serializer.endTag(null, TAG_COLOR_MODE);
serializer.startTag(null, TAG_BRIGHTNESS_VALUE);
- serializer.text(Float.toString(mBrightness));
+ if (!Float.isNaN(mBrightness)) {
+ serializer.text(Float.toString(mBrightness));
+ }
serializer.endTag(null, TAG_BRIGHTNESS_VALUE);
serializer.startTag(null, TAG_BRIGHTNESS_CONFIGURATIONS);
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index d0e518b876dd..a118b2fa37c3 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -20,6 +20,7 @@ import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_ALWAY
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_DEVICE_DISPLAY_GROUP;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_FOCUS;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION;
@@ -32,6 +33,7 @@ import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_TOUCH
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED;
import static com.android.server.display.DisplayDeviceInfo.FLAG_ALWAYS_UNLOCKED;
+import static com.android.server.display.DisplayDeviceInfo.FLAG_DEVICE_DISPLAY_GROUP;
import static com.android.server.display.DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP;
import static com.android.server.display.DisplayDeviceInfo.FLAG_TOUCH_FEEDBACK_DISABLED;
import static com.android.server.display.DisplayDeviceInfo.FLAG_TRUSTED;
@@ -446,6 +448,7 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
mInfo.width = mWidth;
mInfo.height = mHeight;
mInfo.modeId = mMode.getModeId();
+ mInfo.renderFrameRate = mMode.getRefreshRate();
mInfo.defaultModeId = mMode.getModeId();
mInfo.supportedModes = new Display.Mode[] { mMode };
mInfo.densityDpi = mDensityDpi;
@@ -466,6 +469,9 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
mInfo.flags |= FLAG_OWN_DISPLAY_GROUP;
}
}
+ if ((mFlags & VIRTUAL_DISPLAY_FLAG_DEVICE_DISPLAY_GROUP) != 0) {
+ mInfo.flags |= FLAG_DEVICE_DISPLAY_GROUP;
+ }
if ((mFlags & VIRTUAL_DISPLAY_FLAG_SECURE) != 0) {
mInfo.flags |= DisplayDeviceInfo.FLAG_SECURE;
@@ -498,11 +504,15 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
mInfo.flags |= FLAG_TRUSTED;
}
if ((mFlags & VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED) != 0) {
- if ((mFlags & VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP) != 0) {
+ if ((mInfo.flags & DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP) != 0
+ || (mFlags & VIRTUAL_DISPLAY_FLAG_DEVICE_DISPLAY_GROUP) != 0) {
mInfo.flags |= FLAG_ALWAYS_UNLOCKED;
} else {
- Slog.w(TAG, "Ignoring VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED as it "
- + "requires VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP.");
+ Slog.w(
+ TAG,
+ "Ignoring VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED as it requires"
+ + " VIRTUAL_DISPLAY_FLAG_DEVICE_DISPLAY_GROUP or"
+ + " VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP.");
}
}
if ((mFlags & VIRTUAL_DISPLAY_FLAG_TOUCH_FEEDBACK_DISABLED) != 0) {
diff --git a/services/core/java/com/android/server/display/WifiDisplayAdapter.java b/services/core/java/com/android/server/display/WifiDisplayAdapter.java
index c759d98561f9..e8327018e144 100644
--- a/services/core/java/com/android/server/display/WifiDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/WifiDisplayAdapter.java
@@ -646,6 +646,7 @@ final class WifiDisplayAdapter extends DisplayAdapter {
mInfo.width = mWidth;
mInfo.height = mHeight;
mInfo.modeId = mMode.getModeId();
+ mInfo.renderFrameRate = mMode.getRefreshRate();
mInfo.defaultModeId = mMode.getModeId();
mInfo.supportedModes = new Display.Mode[] { mMode };
mInfo.presentationDeadlineNanos = 1000000000L / (int) mRefreshRate; // 1 frame
diff --git a/services/core/java/com/android/server/display/brightness/BrightnessReason.java b/services/core/java/com/android/server/display/brightness/BrightnessReason.java
index d8eacd930d40..b6be713d344b 100644
--- a/services/core/java/com/android/server/display/brightness/BrightnessReason.java
+++ b/services/core/java/com/android/server/display/brightness/BrightnessReason.java
@@ -34,10 +34,9 @@ public final class BrightnessReason {
public static final int REASON_DOZE_DEFAULT = 3;
public static final int REASON_AUTOMATIC = 4;
public static final int REASON_SCREEN_OFF = 5;
- public static final int REASON_VR = 6;
- public static final int REASON_OVERRIDE = 7;
- public static final int REASON_TEMPORARY = 8;
- public static final int REASON_BOOST = 9;
+ public static final int REASON_OVERRIDE = 6;
+ public static final int REASON_TEMPORARY = 7;
+ public static final int REASON_BOOST = 8;
public static final int REASON_MAX = REASON_BOOST;
public static final int MODIFIER_DIMMED = 0x1;
@@ -185,8 +184,6 @@ public final class BrightnessReason {
return "automatic";
case REASON_SCREEN_OFF:
return "screen_off";
- case REASON_VR:
- return "vr";
case REASON_OVERRIDE:
return "override";
case REASON_TEMPORARY:
diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
index 4759b7df69ab..7d05f139c69d 100644
--- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
+++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
@@ -25,6 +25,7 @@ import android.view.Display;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.display.brightness.strategy.BoostBrightnessStrategy;
import com.android.server.display.brightness.strategy.DisplayBrightnessStrategy;
import com.android.server.display.brightness.strategy.DozeBrightnessStrategy;
import com.android.server.display.brightness.strategy.InvalidBrightnessStrategy;
@@ -52,6 +53,8 @@ public class DisplayBrightnessStrategySelector {
private final OverrideBrightnessStrategy mOverrideBrightnessStrategy;
// The brightness strategy used to manage the brightness state in temporary state
private final TemporaryBrightnessStrategy mTemporaryBrightnessStrategy;
+ // The brightness strategy used to manage the brightness state when boost is requested
+ private final BoostBrightnessStrategy mBoostBrightnessStrategy;
// The brightness strategy used to manage the brightness state when the request is invalid.
private final InvalidBrightnessStrategy mInvalidBrightnessStrategy;
@@ -72,6 +75,7 @@ public class DisplayBrightnessStrategySelector {
mScreenOffBrightnessStrategy = injector.getScreenOffBrightnessStrategy();
mOverrideBrightnessStrategy = injector.getOverrideBrightnessStrategy();
mTemporaryBrightnessStrategy = injector.getTemporaryBrightnessStrategy();
+ mBoostBrightnessStrategy = injector.getBoostBrightnessStrategy();
mInvalidBrightnessStrategy = injector.getInvalidBrightnessStrategy();
mAllowAutoBrightnessWhileDozingConfig = context.getResources().getBoolean(
R.bool.config_allowAutoBrightnessWhileDozing);
@@ -89,6 +93,8 @@ public class DisplayBrightnessStrategySelector {
DisplayBrightnessStrategy displayBrightnessStrategy = mInvalidBrightnessStrategy;
if (targetDisplayState == Display.STATE_OFF) {
displayBrightnessStrategy = mScreenOffBrightnessStrategy;
+ } else if (displayPowerRequest.boostScreenBrightness) {
+ displayBrightnessStrategy = mBoostBrightnessStrategy;
} else if (shouldUseDozeBrightnessStrategy(displayPowerRequest)) {
displayBrightnessStrategy = mDozeBrightnessStrategy;
} else if (BrightnessUtils
@@ -170,6 +176,10 @@ public class DisplayBrightnessStrategySelector {
return new TemporaryBrightnessStrategy();
}
+ BoostBrightnessStrategy getBoostBrightnessStrategy() {
+ return new BoostBrightnessStrategy();
+ }
+
InvalidBrightnessStrategy getInvalidBrightnessStrategy() {
return new InvalidBrightnessStrategy();
}
diff --git a/services/core/java/com/android/server/display/brightness/strategy/BoostBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/BoostBrightnessStrategy.java
new file mode 100644
index 000000000000..475ef5012751
--- /dev/null
+++ b/services/core/java/com/android/server/display/brightness/strategy/BoostBrightnessStrategy.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.brightness.strategy;
+
+import android.hardware.display.DisplayManagerInternal;
+import android.os.PowerManager;
+
+import com.android.server.display.DisplayBrightnessState;
+import com.android.server.display.brightness.BrightnessReason;
+import com.android.server.display.brightness.BrightnessUtils;
+
+/**
+ * Manages the brightness of the display when the system brightness boost is requested.
+ */
+public class BoostBrightnessStrategy implements DisplayBrightnessStrategy {
+
+ public BoostBrightnessStrategy() {
+ }
+
+ // Set the brightness to the maximum value when display brightness boost is requested
+ @Override
+ public DisplayBrightnessState updateBrightness(
+ DisplayManagerInternal.DisplayPowerRequest displayPowerRequest) {
+ // Todo(brup): Introduce a validator class and add validations before setting the brightness
+ DisplayBrightnessState displayBrightnessState =
+ BrightnessUtils.constructDisplayBrightnessState(BrightnessReason.REASON_BOOST,
+ PowerManager.BRIGHTNESS_MAX,
+ PowerManager.BRIGHTNESS_MAX);
+ return displayBrightnessState;
+ }
+
+ @Override
+ public String getName() {
+ return "BoostBrightnessStrategy";
+ }
+}
diff --git a/services/core/java/com/android/server/display/layout/DisplayIdProducer.java b/services/core/java/com/android/server/display/layout/DisplayIdProducer.java
new file mode 100644
index 000000000000..3029757cb562
--- /dev/null
+++ b/services/core/java/com/android/server/display/layout/DisplayIdProducer.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.layout;
+
+/**
+ * Interface for producing logical display ids.
+ */
+public interface DisplayIdProducer {
+
+ /**
+ * Generates a new display ID
+ * @param isDefault if requested display is the default display.
+ * @return the next unique logical display Id.
+ */
+ int getId(boolean isDefault);
+}
diff --git a/services/core/java/com/android/server/display/layout/Layout.java b/services/core/java/com/android/server/display/layout/Layout.java
index 7e16ea8b3ace..4a466fdb90f7 100644
--- a/services/core/java/com/android/server/display/layout/Layout.java
+++ b/services/core/java/com/android/server/display/layout/Layout.java
@@ -50,15 +50,33 @@ public class Layout {
return mDisplays.toString();
}
+ @Override
+ public boolean equals(Object obj) {
+
+ if (!(obj instanceof Layout)) {
+ return false;
+ }
+
+ Layout otherLayout = (Layout) obj;
+ return this.mDisplays.equals(otherLayout.mDisplays);
+ }
+
+ @Override
+ public int hashCode() {
+ return mDisplays.hashCode();
+ }
+
/**
* Creates a simple 1:1 LogicalDisplay mapping for the specified DisplayDevice.
*
* @param address Address of the device.
* @param isDefault Indicates if the device is meant to be the default display.
+ * @param isEnabled Indicates if this display is usable and can be switched on
* @return The new layout.
*/
public Display createDisplayLocked(
- @NonNull DisplayAddress address, boolean isDefault, boolean isEnabled) {
+ @NonNull DisplayAddress address, boolean isDefault, boolean isEnabled,
+ DisplayIdProducer idProducer) {
if (contains(address)) {
Slog.w(TAG, "Attempting to add second definition for display-device: " + address);
return null;
@@ -74,7 +92,7 @@ public class Layout {
// Note that the logical display ID is saved into the layout, so when switching between
// different layouts, a logical display can be destroyed and later recreated with the
// same logical display ID.
- final int logicalDisplayId = assignDisplayIdLocked(isDefault);
+ final int logicalDisplayId = idProducer.getId(isDefault);
final Display display = new Display(address, logicalDisplayId, isEnabled);
mDisplays.add(display);
@@ -158,25 +176,64 @@ public class Layout {
* Describes how a {@link LogicalDisplay} is built from {@link DisplayDevice}s.
*/
public static class Display {
+ public static final int POSITION_UNKNOWN = -1;
+ public static final int POSITION_FRONT = 0;
+ public static final int POSITION_REAR = 1;
+
// Address of the display device to map to this display.
private final DisplayAddress mAddress;
// Logical Display ID to apply to this display.
private final int mLogicalDisplayId;
- // Indicates that this display is not usable and should remain off.
+ // Indicates if this display is usable and can be switched on
private final boolean mIsEnabled;
+ // The direction the display faces
+ // {@link DeviceStateToLayoutMap.POSITION_FRONT} or
+ // {@link DeviceStateToLayoutMap.POSITION_REAR}.
+ // {@link DeviceStateToLayoutMap.POSITION_UNKNOWN} is unspecified.
+ private int mPosition;
+
Display(@NonNull DisplayAddress address, int logicalDisplayId, boolean isEnabled) {
mAddress = address;
mLogicalDisplayId = logicalDisplayId;
mIsEnabled = isEnabled;
+ mPosition = POSITION_UNKNOWN;
}
@Override
public String toString() {
- return "{addr: " + mAddress + ", dispId: " + mLogicalDisplayId
- + "(" + (mIsEnabled ? "ON" : "OFF") + ")}";
+ return "{"
+ + "dispId: " + mLogicalDisplayId
+ + "(" + (mIsEnabled ? "ON" : "OFF") + ")"
+ + ", addr: " + mAddress
+ + ((mPosition == POSITION_UNKNOWN) ? "" : ", position: " + mPosition)
+ + "}";
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof Display)) {
+ return false;
+ }
+
+ Display otherDisplay = (Display) obj;
+
+ return otherDisplay.mIsEnabled == this.mIsEnabled
+ && otherDisplay.mPosition == this.mPosition
+ && otherDisplay.mLogicalDisplayId == this.mLogicalDisplayId
+ && this.mAddress.equals(otherDisplay.mAddress);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 1;
+ result = 31 * result + Boolean.hashCode(mIsEnabled);
+ result = 31 * result + mPosition;
+ result = 31 * result + mLogicalDisplayId;
+ result = 31 * result + mAddress.hashCode();
+ return result;
}
public DisplayAddress getAddress() {
@@ -190,5 +247,9 @@ public class Layout {
public boolean isEnabled() {
return mIsEnabled;
}
+
+ public void setPosition(int position) {
+ mPosition = position;
+ }
}
}
diff --git a/services/core/java/com/android/server/graphics/fonts/FontManagerService.java b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java
index 28dc318d3cd1..3c5b0671afa3 100644
--- a/services/core/java/com/android/server/graphics/fonts/FontManagerService.java
+++ b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java
@@ -187,8 +187,8 @@ public final class FontManagerService extends IFontManager.Stub {
}
@Override
- public void setUpFsverity(String filePath, byte[] pkcs7Signature) throws IOException {
- VerityUtils.setUpFsverity(filePath, pkcs7Signature);
+ public void setUpFsverity(String filePath) throws IOException {
+ VerityUtils.setUpFsverity(filePath, /* signature */ (byte[]) null);
}
@Override
diff --git a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
index 457d5b7afe84..6f9360844542 100644
--- a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
+++ b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
@@ -78,7 +78,7 @@ final class UpdatableFontDir {
interface FsverityUtil {
boolean isFromTrustedProvider(String path, byte[] pkcs7Signature);
- void setUpFsverity(String path, byte[] pkcs7Signature) throws IOException;
+ void setUpFsverity(String path) throws IOException;
boolean rename(File src, File dest);
}
@@ -354,8 +354,7 @@ final class UpdatableFontDir {
try {
// Do not parse font file before setting up fs-verity.
// setUpFsverity throws IOException if failed.
- mFsverityUtil.setUpFsverity(tempNewFontFile.getAbsolutePath(),
- pkcs7Signature);
+ mFsverityUtil.setUpFsverity(tempNewFontFile.getAbsolutePath());
} catch (IOException e) {
throw new SystemFontException(
FontManager.RESULT_ERROR_VERIFICATION_FAILURE,
diff --git a/services/core/java/com/android/server/hdmi/ArcTerminationActionFromAvr.java b/services/core/java/com/android/server/hdmi/ArcTerminationActionFromAvr.java
index 4855be68e676..ccb263362abb 100644
--- a/services/core/java/com/android/server/hdmi/ArcTerminationActionFromAvr.java
+++ b/services/core/java/com/android/server/hdmi/ArcTerminationActionFromAvr.java
@@ -15,6 +15,8 @@
*/
package com.android.server.hdmi;
+import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.IHdmiControlCallback;
import android.hardware.tv.cec.V1_0.SendMessageResult;
/**
@@ -33,6 +35,10 @@ public class ArcTerminationActionFromAvr extends HdmiCecFeatureAction {
super(source);
}
+ ArcTerminationActionFromAvr(HdmiCecLocalDevice source, IHdmiControlCallback callback) {
+ super(source, callback);
+ }
+
@Override
boolean start() {
mState = STATE_WAITING_FOR_INITIATE_ARC_RESPONSE;
@@ -47,10 +53,19 @@ public class ArcTerminationActionFromAvr extends HdmiCecFeatureAction {
return false;
}
switch (cmd.getOpcode()) {
+ case Constants.MESSAGE_FEATURE_ABORT:
+ int originalOpcode = cmd.getParams()[0] & 0xFF;
+ if (originalOpcode == Constants.MESSAGE_TERMINATE_ARC) {
+ mState = STATE_ARC_TERMINATED;
+ audioSystem().processArcTermination();
+ finishWithCallback(HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
+ return true;
+ }
+ return false;
case Constants.MESSAGE_REPORT_ARC_TERMINATED:
mState = STATE_ARC_TERMINATED;
audioSystem().processArcTermination();
- finish();
+ finishWithCallback(HdmiControlManager.RESULT_SUCCESS);
return true;
}
return false;
@@ -79,7 +94,7 @@ public class ArcTerminationActionFromAvr extends HdmiCecFeatureAction {
audioSystem().setArcStatus(false);
}
HdmiLogger.debug("Terminate ARC was not successfully sent.");
- finish();
+ finishWithCallback(HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
}
});
}
@@ -88,6 +103,6 @@ public class ArcTerminationActionFromAvr extends HdmiCecFeatureAction {
// Disable ARC if TV didn't respond with <Report ARC Terminated> in time.
audioSystem().setArcStatus(false);
HdmiLogger.debug("handleTerminateArcTimeout");
- finish();
+ finishWithCallback(HdmiControlManager.RESULT_TIMEOUT);
}
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
index ccaa9255dbd4..a026c4b59ec5 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
@@ -458,8 +458,16 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource {
HdmiLogger.debug("ARC is not established between TV and AVR device");
return Constants.ABORT_NOT_IN_CORRECT_MODE;
} else {
- removeAction(ArcTerminationActionFromAvr.class);
- addAndStartAction(new ArcTerminationActionFromAvr(this));
+ if (!getActions(ArcTerminationActionFromAvr.class).isEmpty()
+ && !getActions(ArcTerminationActionFromAvr.class).get(0).mCallbacks.isEmpty()) {
+ IHdmiControlCallback callback =
+ getActions(ArcTerminationActionFromAvr.class).get(0).mCallbacks.get(0);
+ removeAction(ArcTerminationActionFromAvr.class);
+ addAndStartAction(new ArcTerminationActionFromAvr(this, callback));
+ } else {
+ removeAction(ArcTerminationActionFromAvr.class);
+ addAndStartAction(new ArcTerminationActionFromAvr(this));
+ }
return Constants.HANDLED;
}
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 43cd71ae519c..2f15e579b214 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -878,16 +878,30 @@ public class HdmiControlService extends SystemService {
Slog.w(TAG, "Device type doesn't support ARC.");
return;
}
+ boolean isArcEnabled = false;
if (settingValue == SOUNDBAR_MODE_DISABLED && audioSystem != null) {
- if (audioSystem.isArcEnabled()) {
- audioSystem.addAndStartAction(new ArcTerminationActionFromAvr(audioSystem));
- }
+ isArcEnabled = audioSystem.isArcEnabled();
if (isSystemAudioActivated()) {
audioSystem.terminateSystemAudioMode();
}
+ if (isArcEnabled) {
+ if (audioSystem.hasAction(ArcTerminationActionFromAvr.class)) {
+ audioSystem.removeAction(ArcTerminationActionFromAvr.class);
+ }
+ audioSystem.addAndStartAction(new ArcTerminationActionFromAvr(audioSystem,
+ new IHdmiControlCallback.Stub() {
+ @Override
+ public void onComplete(int result) {
+ mAddressAllocated = false;
+ initializeCecLocalDevices(INITIATED_BY_SOUNDBAR_MODE);
+ }
+ }));
+ }
+ }
+ if (!isArcEnabled) {
+ mAddressAllocated = false;
+ initializeCecLocalDevices(INITIATED_BY_SOUNDBAR_MODE);
}
- mAddressAllocated = false;
- initializeCecLocalDevices(INITIATED_BY_SOUNDBAR_MODE);
}
/**
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 199519c19785..0da04a22d31f 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -98,6 +98,7 @@ import android.view.Surface;
import android.view.SurfaceControl;
import android.view.VerifiedInputEvent;
import android.view.ViewConfiguration;
+import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodSubtype;
import com.android.internal.R;
@@ -293,6 +294,9 @@ public class InputManagerService extends IInputManager.Stub
// Manages Keyboard backlight
private final KeyboardBacklightController mKeyboardBacklightController;
+ // Manages Keyboard modifier keys remapping
+ private final KeyRemapper mKeyRemapper;
+
// Maximum number of milliseconds to wait for input event injection.
private static final int INJECTION_TIMEOUT_MILLIS = 30 * 1000;
@@ -407,6 +411,7 @@ public class InputManagerService extends IInputManager.Stub
mBatteryController = new BatteryController(mContext, mNative, injector.getLooper());
mKeyboardBacklightController = new KeyboardBacklightController(mContext, mNative,
mDataStore, injector.getLooper());
+ mKeyRemapper = new KeyRemapper(mContext, mNative, mDataStore, injector.getLooper());
mUseDevInputEventForAudioJack =
mContext.getResources().getBoolean(R.bool.config_useDevInputEventForAudioJack);
@@ -535,6 +540,7 @@ public class InputManagerService extends IInputManager.Stub
mKeyboardLayoutManager.systemRunning();
mBatteryController.systemRunning();
mKeyboardBacklightController.systemRunning();
+ mKeyRemapper.systemRunning();
}
private void reloadDeviceAliases() {
@@ -1184,6 +1190,33 @@ public class InputManagerService extends IInputManager.Stub
keyboardLayoutDescriptor);
}
+ @Override // Binder call
+ public String getKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
+ @UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
+ @NonNull InputMethodSubtype imeSubtype) {
+ return mKeyboardLayoutManager.getKeyboardLayoutForInputDevice(identifier, userId,
+ imeInfo, imeSubtype);
+ }
+
+ @EnforcePermission(Manifest.permission.SET_KEYBOARD_LAYOUT)
+ @Override // Binder call
+ public void setKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
+ @UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
+ @NonNull InputMethodSubtype imeSubtype, String keyboardLayoutDescriptor) {
+ super.setKeyboardLayoutForInputDevice_enforcePermission();
+ mKeyboardLayoutManager.setKeyboardLayoutForInputDevice(identifier, userId, imeInfo,
+ imeSubtype, keyboardLayoutDescriptor);
+ }
+
+ @Override // Binder call
+ public String[] getKeyboardLayoutListForInputDevice(InputDeviceIdentifier identifier,
+ @UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
+ @NonNull InputMethodSubtype imeSubtype) {
+ return mKeyboardLayoutManager.getKeyboardLayoutListForInputDevice(identifier, userId,
+ imeInfo, imeSubtype);
+ }
+
+
public void switchKeyboardLayout(int deviceId, int direction) {
mKeyboardLayoutManager.switchKeyboardLayout(deviceId, direction);
}
@@ -2710,6 +2743,27 @@ public class InputManagerService extends IInputManager.Stub
return mKeyboardLayoutManager.getKeyboardLayoutOverlay(identifier);
}
+ @EnforcePermission(Manifest.permission.REMAP_MODIFIER_KEYS)
+ @Override // Binder call
+ public void remapModifierKey(int fromKey, int toKey) {
+ super.remapModifierKey_enforcePermission();
+ mKeyRemapper.remapKey(fromKey, toKey);
+ }
+
+ @EnforcePermission(Manifest.permission.REMAP_MODIFIER_KEYS)
+ @Override // Binder call
+ public void clearAllModifierKeyRemappings() {
+ super.clearAllModifierKeyRemappings_enforcePermission();
+ mKeyRemapper.clearAllKeyRemappings();
+ }
+
+ @EnforcePermission(Manifest.permission.REMAP_MODIFIER_KEYS)
+ @Override // Binder call
+ public Map<Integer, Integer> getModifierKeyRemapping() {
+ super.getModifierKeyRemapping_enforcePermission();
+ return mKeyRemapper.getKeyRemapping();
+ }
+
// Native callback.
@SuppressWarnings("unused")
private String getDeviceAlias(String uniqueId) {
diff --git a/services/core/java/com/android/server/input/KeyRemapper.java b/services/core/java/com/android/server/input/KeyRemapper.java
new file mode 100644
index 000000000000..950e094a205d
--- /dev/null
+++ b/services/core/java/com/android/server/input/KeyRemapper.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.input;
+
+import android.content.Context;
+import android.hardware.input.InputManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.view.InputDevice;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * A component of {@link InputManagerService} responsible for managing key remappings.
+ *
+ * @hide
+ */
+final class KeyRemapper implements InputManager.InputDeviceListener {
+
+ private static final int MSG_UPDATE_EXISTING_DEVICES = 1;
+ private static final int MSG_REMAP_KEY = 2;
+ private static final int MSG_CLEAR_ALL_REMAPPING = 3;
+
+ private final Context mContext;
+ private final NativeInputManagerService mNative;
+ // The PersistentDataStore should be locked before use.
+ @GuardedBy("mDataStore")
+ private final PersistentDataStore mDataStore;
+ private final Handler mHandler;
+
+ KeyRemapper(Context context, NativeInputManagerService nativeService,
+ PersistentDataStore dataStore, Looper looper) {
+ mContext = context;
+ mNative = nativeService;
+ mDataStore = dataStore;
+ mHandler = new Handler(looper, this::handleMessage);
+ }
+
+ public void systemRunning() {
+ InputManager inputManager = Objects.requireNonNull(
+ mContext.getSystemService(InputManager.class));
+ inputManager.registerInputDeviceListener(this, mHandler);
+
+ Message msg = Message.obtain(mHandler, MSG_UPDATE_EXISTING_DEVICES,
+ inputManager.getInputDeviceIds());
+ mHandler.sendMessage(msg);
+ }
+
+ public void remapKey(int fromKey, int toKey) {
+ Message msg = Message.obtain(mHandler, MSG_REMAP_KEY, fromKey, toKey);
+ mHandler.sendMessage(msg);
+ }
+
+ public void clearAllKeyRemappings() {
+ Message msg = Message.obtain(mHandler, MSG_CLEAR_ALL_REMAPPING);
+ mHandler.sendMessage(msg);
+ }
+
+ public Map<Integer, Integer> getKeyRemapping() {
+ synchronized (mDataStore) {
+ return mDataStore.getKeyRemapping();
+ }
+ }
+
+ private void addKeyRemapping(int fromKey, int toKey) {
+ InputManager inputManager = Objects.requireNonNull(
+ mContext.getSystemService(InputManager.class));
+ for (int deviceId : inputManager.getInputDeviceIds()) {
+ InputDevice inputDevice = inputManager.getInputDevice(deviceId);
+ if (inputDevice != null && !inputDevice.isVirtual() && inputDevice.isFullKeyboard()) {
+ mNative.addKeyRemapping(deviceId, fromKey, toKey);
+ }
+ }
+ }
+
+ private void remapKeyInternal(int fromKey, int toKey) {
+ addKeyRemapping(fromKey, toKey);
+ synchronized (mDataStore) {
+ try {
+ if (fromKey == toKey) {
+ mDataStore.clearMappedKey(fromKey);
+ } else {
+ mDataStore.remapKey(fromKey, toKey);
+ }
+ } finally {
+ mDataStore.saveIfNeeded();
+ }
+ }
+ }
+
+ private void clearAllRemappingsInternal() {
+ synchronized (mDataStore) {
+ try {
+ Map<Integer, Integer> keyRemapping = mDataStore.getKeyRemapping();
+ for (int fromKey : keyRemapping.keySet()) {
+ mDataStore.clearMappedKey(fromKey);
+
+ // Remapping to itself will clear the remapping on native side
+ addKeyRemapping(fromKey, fromKey);
+ }
+ } finally {
+ mDataStore.saveIfNeeded();
+ }
+ }
+ }
+
+ @Override
+ public void onInputDeviceAdded(int deviceId) {
+ InputManager inputManager = Objects.requireNonNull(
+ mContext.getSystemService(InputManager.class));
+ InputDevice inputDevice = inputManager.getInputDevice(deviceId);
+ if (inputDevice != null && !inputDevice.isVirtual() && inputDevice.isFullKeyboard()) {
+ Map<Integer, Integer> remapping = getKeyRemapping();
+ remapping.forEach(
+ (fromKey, toKey) -> mNative.addKeyRemapping(deviceId, fromKey, toKey));
+ }
+ }
+
+ @Override
+ public void onInputDeviceRemoved(int deviceId) {
+ }
+
+ @Override
+ public void onInputDeviceChanged(int deviceId) {
+ }
+
+ private boolean handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_UPDATE_EXISTING_DEVICES:
+ for (int deviceId : (int[]) msg.obj) {
+ onInputDeviceAdded(deviceId);
+ }
+ return true;
+ case MSG_REMAP_KEY:
+ remapKeyInternal(msg.arg1, msg.arg2);
+ return true;
+ case MSG_CLEAR_ALL_REMAPPING:
+ clearAllRemappingsInternal();
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/services/core/java/com/android/server/input/KeyboardLayoutManager.java b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
index c2157a6497de..1bb14aa6c438 100644
--- a/services/core/java/com/android/server/input/KeyboardLayoutManager.java
+++ b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
@@ -17,6 +17,7 @@
package com.android.server.input;
import android.annotation.NonNull;
+import android.annotation.UserIdInt;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
@@ -46,6 +47,8 @@ import android.text.TextUtils;
import android.util.Log;
import android.util.Slog;
import android.view.InputDevice;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodSubtype;
import android.widget.Toast;
import com.android.internal.R;
@@ -142,7 +145,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
@Override
public void onInputDeviceChanged(int deviceId) {
final InputDevice inputDevice = getInputDevice(deviceId);
- if (inputDevice == null) {
+ if (inputDevice == null || inputDevice.isVirtual() || !inputDevice.isFullKeyboard()) {
return;
}
synchronized (mDataStore) {
@@ -545,6 +548,35 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
}
}
+ public String getKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
+ @UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
+ @NonNull InputMethodSubtype imeSubtype) {
+ // TODO(b/259530132): Implement the new keyboard layout API: Returning non-IME specific
+ // layout for now.
+ return getCurrentKeyboardLayoutForInputDevice(identifier);
+ }
+
+ public void setKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
+ @UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
+ @NonNull InputMethodSubtype imeSubtype, String keyboardLayoutDescriptor) {
+ // TODO(b/259530132): Implement the new keyboard layout API: setting non-IME specific
+ // layout for now.
+ setCurrentKeyboardLayoutForInputDevice(identifier, keyboardLayoutDescriptor);
+ }
+
+ public String[] getKeyboardLayoutListForInputDevice(InputDeviceIdentifier identifier,
+ @UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
+ @NonNull InputMethodSubtype imeSubtype) {
+ // TODO(b/259530132): Implement the new keyboard layout API: Returning list of all
+ // layouts for now.
+ KeyboardLayout[] allLayouts = getKeyboardLayouts();
+ String[] allLayoutDesc = new String[allLayouts.length];
+ for (int i = 0; i < allLayouts.length; i++) {
+ allLayoutDesc[i] = allLayouts[i].getDescriptor();
+ }
+ return allLayoutDesc;
+ }
+
public void switchKeyboardLayout(int deviceId, int direction) {
mHandler.obtainMessage(MSG_SWITCH_KEYBOARD_LAYOUT, deviceId, direction).sendToTarget();
}
diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java
index cfa7fb141be1..8781c6e2b934 100644
--- a/services/core/java/com/android/server/input/NativeInputManagerService.java
+++ b/services/core/java/com/android/server/input/NativeInputManagerService.java
@@ -47,6 +47,8 @@ interface NativeInputManagerService {
int getSwitchState(int deviceId, int sourceMask, int sw);
+ void addKeyRemapping(int deviceId, int fromKeyCode, int toKeyCode);
+
boolean hasKeys(int deviceId, int sourceMask, int[] keyCodes, boolean[] keyExists);
int getKeyCodeForKeyLocation(int deviceId, int locationKeyCode);
@@ -235,6 +237,9 @@ interface NativeInputManagerService {
public native int getSwitchState(int deviceId, int sourceMask, int sw);
@Override
+ public native void addKeyRemapping(int deviceId, int fromKeyCode, int toKeyCode);
+
+ @Override
public native boolean hasKeys(int deviceId, int sourceMask, int[] keyCodes,
boolean[] keyExists);
diff --git a/services/core/java/com/android/server/input/PersistentDataStore.java b/services/core/java/com/android/server/input/PersistentDataStore.java
index 1bb10c710839..375377a7510d 100644
--- a/services/core/java/com/android/server/input/PersistentDataStore.java
+++ b/services/core/java/com/android/server/input/PersistentDataStore.java
@@ -27,14 +27,13 @@ import android.view.Surface;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import libcore.io.IoUtils;
import org.xmlpull.v1.XmlPullParserException;
-import com.android.modules.utils.TypedXmlPullParser;
-import com.android.modules.utils.TypedXmlSerializer;
-
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
@@ -64,6 +63,8 @@ import java.util.Set;
final class PersistentDataStore {
static final String TAG = "InputManager";
+ private static final int INVALID_VALUE = -1;
+
// Input device state by descriptor.
private final HashMap<String, InputDeviceState> mInputDevices =
new HashMap<String, InputDeviceState>();
@@ -77,6 +78,9 @@ final class PersistentDataStore {
// True if there are changes to be saved.
private boolean mDirty;
+ // Storing key remapping
+ private Map<Integer, Integer> mKeyRemapping = new HashMap<>();
+
public PersistentDataStore() {
this(new Injector());
}
@@ -187,6 +191,30 @@ final class PersistentDataStore {
return state.getKeyboardBacklightBrightness(lightId);
}
+ public boolean remapKey(int fromKey, int toKey) {
+ loadIfNeeded();
+ if (mKeyRemapping.getOrDefault(fromKey, INVALID_VALUE) == toKey) {
+ return false;
+ }
+ mKeyRemapping.put(fromKey, toKey);
+ setDirty();
+ return true;
+ }
+
+ public boolean clearMappedKey(int key) {
+ loadIfNeeded();
+ if (mKeyRemapping.containsKey(key)) {
+ mKeyRemapping.remove(key);
+ setDirty();
+ }
+ return true;
+ }
+
+ public Map<Integer, Integer> getKeyRemapping() {
+ loadIfNeeded();
+ return new HashMap<>(mKeyRemapping);
+ }
+
public boolean removeUninstalledKeyboardLayouts(Set<String> availableKeyboardLayouts) {
boolean changed = false;
for (InputDeviceState state : mInputDevices.values()) {
@@ -229,6 +257,7 @@ final class PersistentDataStore {
}
private void clearState() {
+ mKeyRemapping.clear();
mInputDevices.clear();
}
@@ -280,7 +309,9 @@ final class PersistentDataStore {
XmlUtils.beginDocument(parser, "input-manager-state");
final int outerDepth = parser.getDepth();
while (XmlUtils.nextElementWithin(parser, outerDepth)) {
- if (parser.getName().equals("input-devices")) {
+ if (parser.getName().equals("key-remapping")) {
+ loadKeyRemappingFromXml(parser);
+ } else if (parser.getName().equals("input-devices")) {
loadInputDevicesFromXml(parser);
}
}
@@ -307,10 +338,31 @@ final class PersistentDataStore {
}
}
+ private void loadKeyRemappingFromXml(TypedXmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ final int outerDepth = parser.getDepth();
+ while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+ if (parser.getName().equals("remap")) {
+ int fromKey = parser.getAttributeInt(null, "from-key");
+ int toKey = parser.getAttributeInt(null, "to-key");
+ mKeyRemapping.put(fromKey, toKey);
+ }
+ }
+ }
+
private void saveToXml(TypedXmlSerializer serializer) throws IOException {
serializer.startDocument(null, true);
serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
serializer.startTag(null, "input-manager-state");
+ serializer.startTag(null, "key-remapping");
+ for (int fromKey : mKeyRemapping.keySet()) {
+ int toKey = mKeyRemapping.get(fromKey);
+ serializer.startTag(null, "remap");
+ serializer.attributeInt(null, "from-key", fromKey);
+ serializer.attributeInt(null, "to-key", toKey);
+ serializer.endTag(null, "remap");
+ }
+ serializer.endTag(null, "key-remapping");
serializer.startTag(null, "input-devices");
for (Map.Entry<String, InputDeviceState> entry : mInputDevices.entrySet()) {
final String descriptor = entry.getKey();
@@ -329,7 +381,6 @@ final class PersistentDataStore {
private static final String[] CALIBRATION_NAME = { "x_scale",
"x_ymix", "x_offset", "y_xmix", "y_scale", "y_offset" };
- private static final int INVALID_VALUE = -1;
private final TouchCalibration[] mTouchCalibration = new TouchCalibration[4];
@Nullable
private String mCurrentKeyboardLayout;
diff --git a/services/core/java/com/android/server/locales/LocaleManagerService.java b/services/core/java/com/android/server/locales/LocaleManagerService.java
index 39b9f1f2aa5d..783a6ae0e495 100644
--- a/services/core/java/com/android/server/locales/LocaleManagerService.java
+++ b/services/core/java/com/android/server/locales/LocaleManagerService.java
@@ -38,6 +38,7 @@ import android.os.Process;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ShellCallback;
+import android.os.SystemProperties;
import android.os.UserHandle;
import android.provider.Settings;
import android.text.TextUtils;
@@ -59,6 +60,10 @@ import java.io.FileDescriptor;
*/
public class LocaleManagerService extends SystemService {
private static final String TAG = "LocaleManagerService";
+ // The feature flag control that allows the active IME to query the locales of the foreground
+ // app.
+ private static final String PROP_ALLOW_IME_QUERY_APP_LOCALE =
+ "i18n.feature.allow_ime_query_app_locale";
final Context mContext;
private final LocaleManagerService.LocaleManagerBinderService mBinderService;
private ActivityTaskManagerInternal mActivityTaskManagerInternal;
@@ -431,6 +436,10 @@ public class LocaleManagerService extends SystemService {
* Checks if the calling app is the current input method.
*/
private boolean isCallerFromCurrentInputMethod(int userId) {
+ if (!SystemProperties.getBoolean(PROP_ALLOW_IME_QUERY_APP_LOCALE, true)) {
+ return false;
+ }
+
String currentInputMethod = Settings.Secure.getStringForUser(
mContext.getContentResolver(),
Settings.Secure.DEFAULT_INPUT_METHOD,
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
index 90245b5ebda9..7f6c2d6ececc 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
@@ -73,6 +73,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledThreadPoolExecutor;
@@ -147,7 +148,6 @@ public class ContextHubService extends IContextHubService.Stub {
private final ScheduledThreadPoolExecutor mDailyMetricTimer =
new ScheduledThreadPoolExecutor(1);
-
// The period of the recurring time
private static final int PERIOD_METRIC_QUERY_DAYS = 1;
@@ -363,11 +363,13 @@ public class ContextHubService extends IContextHubService.Stub {
*/
private void initDefaultClientMap() {
HashMap<Integer, IContextHubClient> defaultClientMap = new HashMap<>();
- for (int contextHubId : mContextHubIdToInfoMap.keySet()) {
+ for (Map.Entry<Integer, ContextHubInfo> entry: mContextHubIdToInfoMap.entrySet()) {
+ int contextHubId = entry.getKey();
+ ContextHubInfo contextHubInfo = entry.getValue();
+
mLastRestartTimestampMap.put(contextHubId,
new AtomicLong(SystemClock.elapsedRealtimeNanos()));
- ContextHubInfo contextHubInfo = mContextHubIdToInfoMap.get(contextHubId);
IContextHubClient client = mClientManager.registerClient(
contextHubInfo, createDefaultClientCallback(contextHubId),
/* attributionTag= */ null, mTransactionManager, mContext.getPackageName());
@@ -1133,6 +1135,26 @@ public class ContextHubService extends IContextHubService.Stub {
mTransactionManager.addTransaction(transaction);
}
+ @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
+ /**
+ * Queries for a list of preloaded nanoapp IDs from the specified Context Hub.
+ *
+ * @param hubInfo The Context Hub to query a list of nanoapps from.
+ * @return The list of 64-bit IDs of the preloaded nanoapps.
+ * @throws NullPointerException if hubInfo is null
+ */
+ @Override
+ public long[] getPreloadedNanoAppIds(ContextHubInfo hubInfo) throws RemoteException {
+ super.getPreloadedNanoAppIds_enforcePermission();
+ Objects.requireNonNull(hubInfo, "hubInfo cannot be null");
+
+ long[] nanoappIds = mContextHubWrapper.getPreloadedNanoappIds();
+ if (nanoappIds == null) {
+ return new long[0];
+ }
+ return nanoappIds;
+ }
+
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
@@ -1160,6 +1182,10 @@ public class ContextHubService extends IContextHubService.Stub {
mNanoAppStateManager.foreachNanoAppInstanceInfo((info) -> pw.println(info));
pw.println("");
+ pw.println("=================== PRELOADED NANOAPPS ====================");
+ dumpPreloadedNanoapps(pw);
+
+ pw.println("");
pw.println("=================== CLIENTS ====================");
pw.println(mClientManager);
@@ -1201,6 +1227,21 @@ public class ContextHubService extends IContextHubService.Stub {
proto.flush();
}
+ /**
+ * Dumps preloaded nanoapps to the console
+ */
+ private void dumpPreloadedNanoapps(PrintWriter pw) {
+ if (mContextHubWrapper == null) {
+ return;
+ }
+
+ long[] preloadedNanoappIds = mContextHubWrapper.getPreloadedNanoappIds();
+ for (long preloadedNanoappId: preloadedNanoappIds) {
+ pw.print("ID: 0x");
+ pw.println(Long.toHexString(preloadedNanoappId));
+ }
+ }
+
private void checkPermissions() {
ContextHubServiceUtil.checkPermissions(mContext);
}
diff --git a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
index 48152b40aaf6..f55ae6e8aed4 100644
--- a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
+++ b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
@@ -359,6 +359,14 @@ public abstract class IContextHubWrapper {
public abstract int queryNanoapps(int contextHubId) throws RemoteException;
/**
+ * Provides the list of preloaded nanoapp IDs on the system. The output of this API must
+ * not change.
+ *
+ * @return The list of preloaded nanoapp IDs
+ */
+ public abstract long[] getPreloadedNanoappIds();
+
+ /**
* Registers a callback with the Context Hub.
*
* @param contextHubId The ID of the Context Hub to register the callback with.
@@ -683,6 +691,20 @@ public abstract class IContextHubWrapper {
}
}
+ public long[] getPreloadedNanoappIds() {
+ android.hardware.contexthub.IContextHub hub = getHub();
+ if (hub == null) {
+ return null;
+ }
+
+ try {
+ return hub.getPreloadedNanoappIds();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Exception while getting preloaded nanoapp IDs: " + e.getMessage());
+ return null;
+ }
+ }
+
public void registerExistingCallback(int contextHubId) {
android.hardware.contexthub.IContextHub hub = getHub();
if (hub == null) {
@@ -863,6 +885,10 @@ public abstract class IContextHubWrapper {
mHub.queryApps(contextHubId));
}
+ public long[] getPreloadedNanoappIds() {
+ return new long[0];
+ }
+
public void registerCallback(int contextHubId, ICallback callback) throws RemoteException {
mHidlCallbackMap.put(contextHubId,
new ContextHubWrapperHidlCallback(contextHubId, callback));
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 25e71e8ceca1..2dc1e1cc5747 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -21,6 +21,8 @@ import static android.Manifest.permission.MANAGE_BIOMETRIC;
import static android.Manifest.permission.READ_CONTACTS;
import static android.Manifest.permission.SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS;
import static android.Manifest.permission.SET_INITIAL_LOCK;
+import static android.app.admin.DevicePolicyManager.DEPRECATE_USERMANAGERINTERNAL_DEVICEPOLICY_DEFAULT;
+import static android.app.admin.DevicePolicyManager.DEPRECATE_USERMANAGERINTERNAL_DEVICEPOLICY_FLAG;
import static android.app.admin.DevicePolicyResources.Strings.Core.PROFILE_ENCRYPTED_DETAIL;
import static android.app.admin.DevicePolicyResources.Strings.Core.PROFILE_ENCRYPTED_MESSAGE;
import static android.app.admin.DevicePolicyResources.Strings.Core.PROFILE_ENCRYPTED_TITLE;
@@ -69,7 +71,7 @@ import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.database.ContentObserver;
import android.database.sqlite.SQLiteDatabase;
-import android.hardware.authsecret.V1_0.IAuthSecret;
+import android.hardware.authsecret.IAuthSecret;
import android.hardware.biometrics.BiometricManager;
import android.hardware.face.Face;
import android.hardware.face.FaceManager;
@@ -91,6 +93,7 @@ import android.os.UserHandle;
import android.os.UserManager;
import android.os.storage.IStorageManager;
import android.os.storage.StorageManager;
+import android.provider.DeviceConfig;
import android.provider.Settings;
import android.provider.Settings.Secure;
import android.security.AndroidKeyStoreMaintenance;
@@ -265,7 +268,8 @@ public class LockSettingsService extends ILockSettings.Stub {
protected boolean mHasSecureLockScreen;
protected IGateKeeperService mGateKeeperService;
- protected IAuthSecret mAuthSecretService;
+ protected IAuthSecret mAuthSecretServiceAidl;
+ protected android.hardware.authsecret.V1_0.IAuthSecret mAuthSecretServiceHidl;
private static final String GSI_RUNNING_PROP = "ro.gsid.image_running";
@@ -550,16 +554,6 @@ public class LockSettingsService extends ILockSettings.Stub {
return (BiometricManager) mContext.getSystemService(Context.BIOMETRIC_SERVICE);
}
- public int settingsGlobalGetInt(ContentResolver contentResolver, String keyName,
- int defaultValue) {
- return Settings.Global.getInt(contentResolver, keyName, defaultValue);
- }
-
- public int settingsSecureGetInt(ContentResolver contentResolver, String keyName,
- int defaultValue, int userId) {
- return Settings.Secure.getIntForUser(contentResolver, keyName, defaultValue, userId);
- }
-
public java.security.KeyStore getJavaKeyStore() {
try {
java.security.KeyStore ks = java.security.KeyStore.getInstance(
@@ -843,12 +837,19 @@ public class LockSettingsService extends ILockSettings.Stub {
}
private void getAuthSecretHal() {
- try {
- mAuthSecretService = IAuthSecret.getService(/* retry */ true);
- } catch (NoSuchElementException e) {
- Slog.i(TAG, "Device doesn't implement AuthSecret HAL");
- } catch (RemoteException e) {
- Slog.w(TAG, "Failed to get AuthSecret HAL", e);
+ mAuthSecretServiceAidl = IAuthSecret.Stub.asInterface(ServiceManager.
+ waitForDeclaredService(IAuthSecret.DESCRIPTOR + "/default"));
+ if (mAuthSecretServiceAidl == null) {
+ Slog.i(TAG, "Device doesn't implement AuthSecret HAL(aidl), try to get hidl version");
+
+ try {
+ mAuthSecretServiceHidl =
+ android.hardware.authsecret.V1_0.IAuthSecret.getService(/* retry */ true);
+ } catch (NoSuchElementException e) {
+ Slog.i(TAG, "Device doesn't implement AuthSecret HAL(hidl)");
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to get AuthSecret HAL(hidl)", e);
+ }
}
}
@@ -1027,9 +1028,9 @@ public class LockSettingsService extends ILockSettings.Stub {
private void enforceFrpResolved() {
final ContentResolver cr = mContext.getContentResolver();
- final boolean inSetupWizard = mInjector.settingsSecureGetInt(cr,
+ final boolean inSetupWizard = Settings.Secure.getIntForUser(cr,
Settings.Secure.USER_SETUP_COMPLETE, 0, UserHandle.USER_SYSTEM) == 0;
- final boolean secureFrp = mInjector.settingsSecureGetInt(cr,
+ final boolean secureFrp = Settings.Secure.getIntForUser(cr,
Settings.Secure.SECURE_FRP_MODE, 0, UserHandle.USER_SYSTEM) == 1;
if (inSetupWizard && secureFrp) {
throw new SecurityException("Cannot change credential in SUW while factory reset"
@@ -2155,7 +2156,7 @@ public class LockSettingsService extends ILockSettings.Stub {
if (credential == null || credential.isNone()) {
throw new IllegalArgumentException("Credential can't be null or empty");
}
- if (userId == USER_FRP && mInjector.settingsGlobalGetInt(mContext.getContentResolver(),
+ if (userId == USER_FRP && Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.DEVICE_PROVISIONED, 0) != 0) {
Slog.e(TAG, "FRP credential can only be verified prior to provisioning.");
return VerifyCredentialResponse.ERROR;
@@ -2611,17 +2612,25 @@ public class LockSettingsService extends ILockSettings.Stub {
// If the given user is the primary user, pass the auth secret to the HAL. Only the system
// user can be primary. Check for the system user ID before calling getUserInfo(), as other
// users may still be under construction.
- if (mAuthSecretService != null && userId == UserHandle.USER_SYSTEM &&
+ if (userId == UserHandle.USER_SYSTEM &&
mUserManager.getUserInfo(userId).isPrimary()) {
- try {
- final byte[] rawSecret = sp.deriveVendorAuthSecret();
- final ArrayList<Byte> secret = new ArrayList<>(rawSecret.length);
- for (int i = 0; i < rawSecret.length; ++i) {
- secret.add(rawSecret[i]);
+ final byte[] rawSecret = sp.deriveVendorAuthSecret();
+ if (mAuthSecretServiceAidl != null) {
+ try {
+ mAuthSecretServiceAidl.setPrimaryUserCredential(rawSecret);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to pass primary user secret to AuthSecret HAL(aidl)", e);
+ }
+ } else if (mAuthSecretServiceHidl != null) {
+ try {
+ final ArrayList<Byte> secret = new ArrayList<>(rawSecret.length);
+ for (int i = 0; i < rawSecret.length; ++i) {
+ secret.add(rawSecret[i]);
+ }
+ mAuthSecretServiceHidl.primaryUserCredential(secret);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to pass primary user secret to AuthSecret HAL(hidl)", e);
}
- mAuthSecretService.primaryUserCredential(secret);
- } catch (RemoteException e) {
- Slog.w(TAG, "Failed to pass primary user secret to AuthSecret HAL", e);
}
}
}
@@ -3177,18 +3186,34 @@ public class LockSettingsService extends ILockSettings.Stub {
* if we are running an automotive build.
*/
private void disableEscrowTokenOnNonManagedDevicesIfNeeded(int userId) {
- final UserManagerInternal userManagerInternal = mInjector.getUserManagerInternal();
+ // TODO(b/258213147): Remove
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER,
+ DEPRECATE_USERMANAGERINTERNAL_DEVICEPOLICY_FLAG,
+ DEPRECATE_USERMANAGERINTERNAL_DEVICEPOLICY_DEFAULT)) {
- // Managed profile should have escrow enabled
- if (userManagerInternal.isUserManaged(userId)) {
- Slog.i(TAG, "Managed profile can have escrow token");
- return;
- }
+ if (mInjector.getDeviceStateCache().isUserOrganizationManaged(userId)) {
+ Slog.i(TAG, "Organization managed users can have escrow token");
+ return;
+ }
+ } else {
+ final UserManagerInternal userManagerInternal = mInjector.getUserManagerInternal();
- // Devices with Device Owner should have escrow enabled on all users.
- if (userManagerInternal.isDeviceManaged()) {
- Slog.i(TAG, "Corp-owned device can have escrow token");
- return;
+ // Managed profile should have escrow enabled
+ if (userManagerInternal.isUserManaged(userId)) {
+ Slog.i(TAG, "Managed profile can have escrow token");
+ return;
+ }
+
+ // Devices with Device Owner should have escrow enabled on all users.
+ if (userManagerInternal.isDeviceManaged()) {
+ Slog.i(TAG, "Corp-owned device can have escrow token");
+ return;
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
}
// If the device is yet to be provisioned (still in SUW), there is still
@@ -3282,6 +3307,7 @@ public class LockSettingsService extends ILockSettings.Stub {
for (UserInfo user : users) {
if (userOwnsFrpCredential(mContext, user)) {
if (!isUserSecure(user.id)) {
+ Slogf.d(TAG, "Clearing FRP credential tied to user %d", user.id);
mStorage.writePersistentDataBlock(PersistentData.TYPE_NONE, user.id,
0, null);
}
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
index 807ba3cf4463..473c4b6e0e75 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
@@ -550,7 +550,7 @@ class LockSettingsStorage {
mCache.clear();
}
- @Nullable @VisibleForTesting
+ @Nullable
PersistentDataBlockManagerInternal getPersistentDataBlockManager() {
if (mPersistentDataBlockManagerInternal == null) {
mPersistentDataBlockManagerInternal =
diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
index 73a16fdbc7c9..acd7cc1f0ddb 100644
--- a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
+++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
@@ -32,6 +32,7 @@ import android.hardware.weaver.V1_0.WeaverStatus;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.UserManager;
+import android.provider.Settings;
import android.security.GateKeeper;
import android.security.Scrypt;
import android.service.gatekeeper.GateKeeperResponse;
@@ -457,6 +458,11 @@ public class SyntheticPasswordManager {
mPasswordSlotManager = passwordSlotManager;
}
+ private boolean isDeviceProvisioned() {
+ return Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.DEVICE_PROVISIONED, 0) != 0;
+ }
+
@VisibleForTesting
protected IWeaver getWeaverService() throws RemoteException {
try {
@@ -770,6 +776,17 @@ public class SyntheticPasswordManager {
private int getNextAvailableWeaverSlot() {
Set<Integer> usedSlots = getUsedWeaverSlots();
usedSlots.addAll(mPasswordSlotManager.getUsedSlots());
+ // If the device is not yet provisioned, then the Weaver slot used by the FRP credential may
+ // be still needed and must not be reused yet. (This *should* instead check "has FRP been
+ // resolved yet?", which would allow reusing the slot a bit earlier. However, the
+ // SECURE_FRP_MODE setting gets set to 1 too late for it to be used here.)
+ if (!isDeviceProvisioned()) {
+ PersistentData persistentData = mStorage.readPersistentDataBlock();
+ if (persistentData != null && persistentData.type == PersistentData.TYPE_SP_WEAVER) {
+ int slot = persistentData.userId; // Note: field name is misleading
+ usedSlots.add(slot);
+ }
+ }
for (int i = 0; i < mWeaverConfig.slots; i++) {
if (!usedSlots.contains(i)) {
return i;
@@ -814,9 +831,14 @@ public class SyntheticPasswordManager {
protectorSecret = transformUnderWeaverSecret(stretchedLskf, weaverSecret);
} else {
- // Weaver is unavailable, so make the protector use Gatekeeper to verify the LSKF
- // instead. However, skip Gatekeeper when the LSKF is empty, since it wouldn't give any
- // benefit in that case as Gatekeeper isn't expected to provide secure deletion.
+ // Weaver is unavailable, so make the protector use Gatekeeper (GK) to verify the LSKF.
+ //
+ // However, skip GK when the LSKF is empty. There are two reasons for this, one
+ // performance and one correctness. The performance reason is that GK wouldn't give any
+ // benefit with an empty LSKF anyway, since GK isn't expected to provide secure
+ // deletion. The correctness reason is that it is unsafe to enroll a password in the
+ // 'fakeUserId' GK range on an FRP-protected device that is in the setup wizard with FRP
+ // not passed yet, as that may overwrite the enrollment used by the FRP credential.
if (!credential.isNone()) {
// In case GK enrollment leaves persistent state around (in RPMB), this will nuke
// them to prevent them from accumulating and causing problems.
@@ -908,12 +930,40 @@ public class SyntheticPasswordManager {
}
}
+ private static boolean isNoneCredential(PasswordData pwd) {
+ return pwd == null || pwd.credentialType == LockPatternUtils.CREDENTIAL_TYPE_NONE;
+ }
+
+ private boolean shouldSynchronizeFrpCredential(@Nullable PasswordData pwd, int userId) {
+ if (mStorage.getPersistentDataBlockManager() == null) {
+ return false;
+ }
+ UserInfo userInfo = mUserManager.getUserInfo(userId);
+ if (!LockPatternUtils.userOwnsFrpCredential(mContext, userInfo)) {
+ return false;
+ }
+ // When initializing the synthetic password of the user that will own the FRP credential,
+ // the FRP data block must not be cleared if the device isn't provisioned yet, since in this
+ // case the old value of the block may still be needed for the FRP authentication step. The
+ // FRP data block will instead be cleared later, by
+ // LockSettingsService.DeviceProvisionedObserver.clearFrpCredentialIfOwnerNotSecure().
+ //
+ // Don't check the SECURE_FRP_MODE setting here, as it gets set to 1 too late.
+ //
+ // Don't delay anything for a nonempty credential. A nonempty credential can be set before
+ // the device has been provisioned, but it's guaranteed to be after FRP was resolved.
+ if (isNoneCredential(pwd) && !isDeviceProvisioned()) {
+ Slog.d(TAG, "Not clearing FRP credential yet because device is not yet provisioned");
+ return false;
+ }
+ return true;
+ }
+
private void synchronizeFrpPassword(@Nullable PasswordData pwd, int requestedQuality,
int userId) {
- if (mStorage.getPersistentDataBlockManager() != null
- && LockPatternUtils.userOwnsFrpCredential(mContext,
- mUserManager.getUserInfo(userId))) {
- if (pwd != null && pwd.credentialType != LockPatternUtils.CREDENTIAL_TYPE_NONE) {
+ if (shouldSynchronizeFrpCredential(pwd, userId)) {
+ Slogf.d(TAG, "Syncing Gatekeeper-based FRP credential tied to user %d", userId);
+ if (!isNoneCredential(pwd)) {
mStorage.writePersistentDataBlock(PersistentData.TYPE_SP, userId, requestedQuality,
pwd.toBytes());
} else {
@@ -924,10 +974,9 @@ public class SyntheticPasswordManager {
private void synchronizeWeaverFrpPassword(@Nullable PasswordData pwd, int requestedQuality,
int userId, int weaverSlot) {
- if (mStorage.getPersistentDataBlockManager() != null
- && LockPatternUtils.userOwnsFrpCredential(mContext,
- mUserManager.getUserInfo(userId))) {
- if (pwd != null && pwd.credentialType != LockPatternUtils.CREDENTIAL_TYPE_NONE) {
+ if (shouldSynchronizeFrpCredential(pwd, userId)) {
+ Slogf.d(TAG, "Syncing Weaver-based FRP credential tied to user %d", userId);
+ if (!isNoneCredential(pwd)) {
mStorage.writePersistentDataBlock(PersistentData.TYPE_SP_WEAVER, weaverSlot,
requestedQuality, pwd.toBytes());
} else {
@@ -1058,7 +1107,7 @@ public class SyntheticPasswordManager {
AuthenticationResult result = new AuthenticationResult();
if (protectorId == SyntheticPasswordManager.NULL_PROTECTOR_ID) {
- // This should never happen, due to the migration done in LSS.bootCompleted().
+ // This should never happen, due to the migration done in LSS.onThirdPartyAppsStarted().
Slogf.wtf(TAG, "Synthetic password not found for user %d", userId);
result.gkResponse = VerifyCredentialResponse.ERROR;
return result;
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index d08150cf0a56..e51ed1b520a9 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -2046,6 +2046,11 @@ public class MediaSessionService extends SystemService implements Monitor {
int controllerUid) {
final int uid = Binder.getCallingUid();
final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier();
+ if (LocalServices.getService(PackageManagerInternal.class)
+ .filterAppAccess(controllerPackageName, uid, userId)) {
+ // The controllerPackageName is not visible to the caller.
+ return false;
+ }
final long token = Binder.clearCallingIdentity();
try {
// Don't perform check between controllerPackageName and controllerUid.
diff --git a/services/core/java/com/android/server/notification/NotificationRecordLogger.java b/services/core/java/com/android/server/notification/NotificationRecordLogger.java
index ef1e11ccad55..4031c833f3c1 100644
--- a/services/core/java/com/android/server/notification/NotificationRecordLogger.java
+++ b/services/core/java/com/android/server/notification/NotificationRecordLogger.java
@@ -18,8 +18,8 @@ package com.android.server.notification;
import static android.service.notification.NotificationListenerService.REASON_ASSISTANT_CANCEL;
import static android.service.notification.NotificationListenerService.REASON_CANCEL;
+import static android.service.notification.NotificationListenerService.REASON_CLEAR_DATA;
import static android.service.notification.NotificationListenerService.REASON_CLICK;
-import static android.service.notification.NotificationListenerService.REASON_TIMEOUT;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -172,7 +172,12 @@ public interface NotificationRecordLogger {
NOTIFICATION_CANCEL_SNOOZED(181),
@UiEvent(doc = "Notification was canceled due to timeout")
NOTIFICATION_CANCEL_TIMEOUT(182),
- // Values 183-189 reserved for future system dismissal reasons
+ @UiEvent(doc = "Notification was canceled due to the backing channel being deleted")
+ NOTIFICATION_CANCEL_CHANNEL_REMOVED(1261),
+ @UiEvent(doc = "Notification was canceled due to the app's storage being cleared")
+ NOTIFICATION_CANCEL_CLEAR_DATA(1262),
+ // Values above this line must remain in the same order as the corresponding
+ // NotificationCancelReason enum values.
@UiEvent(doc = "Notification was canceled due to user dismissal of a peeking notification.")
NOTIFICATION_CANCEL_USER_PEEK(190),
@UiEvent(doc = "Notification was canceled due to user dismissal from the always-on display")
@@ -208,7 +213,7 @@ public interface NotificationRecordLogger {
// Most cancel reasons do not have a meaningful surface. Reason codes map directly
// to NotificationCancelledEvent codes.
if (surface == NotificationStats.DISMISSAL_OTHER) {
- if ((REASON_CLICK <= reason) && (reason <= REASON_TIMEOUT)) {
+ if ((REASON_CLICK <= reason) && (reason <= REASON_CLEAR_DATA)) {
return NotificationCancelledEvent.values()[reason];
}
if (reason == REASON_ASSISTANT_CANCEL) {
diff --git a/services/core/java/com/android/server/notification/SnoozeHelper.java b/services/core/java/com/android/server/notification/SnoozeHelper.java
index 4bbd40d93c36..5f8572b2bbef 100644
--- a/services/core/java/com/android/server/notification/SnoozeHelper.java
+++ b/services/core/java/com/android/server/notification/SnoozeHelper.java
@@ -59,6 +59,9 @@ public final class SnoozeHelper {
static final int CONCURRENT_SNOOZE_LIMIT = 500;
+ // A safe size for strings to be put in persistent storage, to avoid breaking the XML write.
+ static final int MAX_STRING_LENGTH = 1000;
+
protected static final String XML_TAG_NAME = "snoozed-notifications";
private static final String XML_SNOOZED_NOTIFICATION = "notification";
@@ -200,7 +203,7 @@ public final class SnoozeHelper {
scheduleRepost(key, duration);
Long activateAt = System.currentTimeMillis() + duration;
synchronized (mLock) {
- mPersistedSnoozedNotifications.put(key, activateAt);
+ mPersistedSnoozedNotifications.put(getTrimmedString(key), activateAt);
}
}
@@ -210,7 +213,10 @@ public final class SnoozeHelper {
protected void snooze(NotificationRecord record, String contextId) {
if (contextId != null) {
synchronized (mLock) {
- mPersistedSnoozedNotificationsWithContext.put(record.getKey(), contextId);
+ mPersistedSnoozedNotificationsWithContext.put(
+ getTrimmedString(record.getKey()),
+ getTrimmedString(contextId)
+ );
}
}
snooze(record);
@@ -225,6 +231,13 @@ public final class SnoozeHelper {
}
}
+ private String getTrimmedString(String key) {
+ if (key != null && key.length() > MAX_STRING_LENGTH) {
+ return key.substring(0, MAX_STRING_LENGTH);
+ }
+ return key;
+ }
+
protected boolean cancel(int userId, String pkg, String tag, int id) {
synchronized (mLock) {
final Set<Map.Entry<String, NotificationRecord>> records =
@@ -293,10 +306,12 @@ public final class SnoozeHelper {
}
protected void repost(String key, int userId, boolean muteOnReturn) {
+ final String trimmedKey = getTrimmedString(key);
+
NotificationRecord record;
synchronized (mLock) {
- mPersistedSnoozedNotifications.remove(key);
- mPersistedSnoozedNotificationsWithContext.remove(key);
+ mPersistedSnoozedNotifications.remove(trimmedKey);
+ mPersistedSnoozedNotificationsWithContext.remove(trimmedKey);
record = mSnoozedNotifications.remove(key);
}
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index 3421eb727363..79f2b3f42e6f 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -379,6 +379,8 @@ public final class OverlayManagerService extends SystemService {
final String packageName = data.getSchemeSpecificPart();
final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
+ final boolean systemUpdateUninstall =
+ intent.getBooleanExtra(Intent.EXTRA_SYSTEM_UPDATE_UNINSTALL, false);
final int[] userIds;
final int extraUid = intent.getIntExtra(Intent.EXTRA_UID, UserHandle.USER_NULL);
@@ -405,7 +407,7 @@ public final class OverlayManagerService extends SystemService {
break;
case ACTION_PACKAGE_REMOVED:
if (replacing) {
- onPackageReplacing(packageName, userIds);
+ onPackageReplacing(packageName, systemUpdateUninstall, userIds);
} else {
onPackageRemoved(packageName, userIds);
}
@@ -463,7 +465,7 @@ public final class OverlayManagerService extends SystemService {
}
private void onPackageReplacing(@NonNull final String packageName,
- @NonNull final int[] userIds) {
+ boolean systemUpdateUninstall, @NonNull final int[] userIds) {
try {
traceBegin(TRACE_TAG_RRO, "OMS#onPackageReplacing " + packageName);
for (int userId : userIds) {
@@ -472,8 +474,8 @@ public final class OverlayManagerService extends SystemService {
packageName, userId);
if (pkg != null && !mPackageManager.isInstantApp(packageName, userId)) {
try {
- updateTargetPackagesLocked(
- mImpl.onPackageReplacing(packageName, userId));
+ updateTargetPackagesLocked(mImpl.onPackageReplacing(packageName,
+ systemUpdateUninstall, userId));
} catch (OperationFailedException e) {
Slog.e(TAG, "onPackageReplacing internal error", e);
}
diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
index 6ffe60d528ec..9d5830c9ec15 100644
--- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
+++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
@@ -21,6 +21,7 @@ import static android.content.om.OverlayInfo.STATE_ENABLED;
import static android.content.om.OverlayInfo.STATE_MISSING_TARGET;
import static android.content.om.OverlayInfo.STATE_NO_IDMAP;
import static android.content.om.OverlayInfo.STATE_OVERLAY_IS_BEING_REPLACED;
+import static android.content.om.OverlayInfo.STATE_SYSTEM_UPDATE_UNINSTALL;
import static android.content.om.OverlayInfo.STATE_TARGET_IS_BEING_REPLACED;
import static android.os.UserHandle.USER_SYSTEM;
@@ -78,6 +79,7 @@ final class OverlayManagerServiceImpl {
// Flags to use in conjunction with updateState.
private static final int FLAG_OVERLAY_IS_BEING_REPLACED = 1 << 1;
+ private static final int FLAG_SYSTEM_UPDATE_UNINSTALL = 1 << 2;
private final PackageManagerHelper mPackageManager;
private final IdmapManager mIdmapManager;
@@ -275,9 +277,13 @@ final class OverlayManagerServiceImpl {
}
@NonNull
- Set<UserPackage> onPackageReplacing(@NonNull final String pkgName, final int userId)
- throws OperationFailedException {
- return reconcileSettingsForPackage(pkgName, userId, FLAG_OVERLAY_IS_BEING_REPLACED);
+ Set<UserPackage> onPackageReplacing(@NonNull final String pkgName,
+ boolean systemUpdateUninstall, final int userId) throws OperationFailedException {
+ int flags = FLAG_OVERLAY_IS_BEING_REPLACED;
+ if (systemUpdateUninstall) {
+ flags |= FLAG_SYSTEM_UPDATE_UNINSTALL;
+ }
+ return reconcileSettingsForPackage(pkgName, userId, flags);
}
@NonNull
@@ -840,6 +846,10 @@ final class OverlayManagerServiceImpl {
return STATE_OVERLAY_IS_BEING_REPLACED;
}
+ if ((flags & FLAG_SYSTEM_UPDATE_UNINSTALL) != 0) {
+ return STATE_SYSTEM_UPDATE_UNINSTALL;
+ }
+
if (targetPackage == null) {
return STATE_MISSING_TARGET;
}
diff --git a/services/core/java/com/android/server/pm/AppStateHelper.java b/services/core/java/com/android/server/pm/AppStateHelper.java
index 9ea350f265b6..e6e8212ac16a 100644
--- a/services/core/java/com/android/server/pm/AppStateHelper.java
+++ b/services/core/java/com/android/server/pm/AppStateHelper.java
@@ -16,15 +16,22 @@
package com.android.server.pm;
+import static android.media.AudioAttributes.USAGE_VOICE_COMMUNICATION;
+import static android.media.AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING;
+
import android.app.ActivityManager;
import android.app.ActivityManager.RunningAppProcessInfo;
+import android.app.ActivityManagerInternal;
import android.content.Context;
+import android.media.AudioManager;
import android.media.IAudioService;
import android.os.ServiceManager;
+import android.telecom.TelecomManager;
import android.text.TextUtils;
import android.util.ArraySet;
import com.android.internal.util.ArrayUtils;
+import com.android.server.LocalServices;
import java.util.ArrayList;
import java.util.List;
@@ -72,6 +79,43 @@ public class AppStateHelper {
}
/**
+ * True if any app is using voice communication.
+ */
+ private boolean hasVoiceCall() {
+ var am = mContext.getSystemService(AudioManager.class);
+ try {
+ for (var apc : am.getActivePlaybackConfigurations()) {
+ if (!apc.isActive()) {
+ continue;
+ }
+ var usage = apc.getAudioAttributes().getUsage();
+ if (usage == USAGE_VOICE_COMMUNICATION
+ || usage == USAGE_VOICE_COMMUNICATION_SIGNALLING) {
+ return true;
+ }
+ }
+ } catch (Exception ignore) {
+ }
+ return false;
+ }
+
+ /**
+ * True if the app is recording audio.
+ */
+ private boolean isRecordingAudio(String packageName) {
+ var am = mContext.getSystemService(AudioManager.class);
+ try {
+ for (var arc : am.getActiveRecordingConfigurations()) {
+ if (TextUtils.equals(arc.getClientPackageName(), packageName)) {
+ return true;
+ }
+ }
+ } catch (Exception ignore) {
+ }
+ return false;
+ }
+
+ /**
* True if the app is in the foreground.
*/
private boolean isAppForeground(String packageName) {
@@ -89,8 +133,7 @@ public class AppStateHelper {
* True if the app is playing/recording audio.
*/
private boolean hasActiveAudio(String packageName) {
- // TODO(b/235306967): also check recording
- return hasAudioFocus(packageName);
+ return hasAudioFocus(packageName) || isRecordingAudio(packageName);
}
/**
@@ -143,16 +186,16 @@ public class AppStateHelper {
* True if there is an ongoing phone call.
*/
public boolean isInCall() {
- // To be implemented
- return false;
+ // TelecomManager doesn't handle the case where some apps don't implement ConnectionService.
+ // We check apps using voice communication to detect if the device is in call.
+ var tm = mContext.getSystemService(TelecomManager.class);
+ return tm.isInCall() || hasVoiceCall();
}
/**
* Returns a list of packages which depend on {@code packageNames}. These are the packages
* that will be affected when updating {@code packageNames} and should participate in
* the evaluation of install constraints.
- *
- * TODO(b/235306967): Also include bounded services as dependency.
*/
public List<String> getDependencyPackages(List<String> packageNames) {
var results = new ArraySet<String>();
@@ -167,6 +210,10 @@ public class AppStateHelper {
}
}
}
+ var amInternal = LocalServices.getService(ActivityManagerInternal.class);
+ for (var packageName : packageNames) {
+ results.addAll(amInternal.getClientPackages(packageName));
+ }
return new ArrayList<>(results);
}
}
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 5d01aebab29b..7553370bf4c6 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -2259,20 +2259,26 @@ final class InstallPackageHelper {
incrementalStorages.add(storage);
}
- try {
- if (!VerityUtils.hasFsverity(pkg.getBaseApkPath())) {
- VerityUtils.setUpFsverity(pkg.getBaseApkPath(), (byte[]) null);
- }
- for (String path : pkg.getSplitCodePaths()) {
- if (!VerityUtils.hasFsverity(path)) {
- VerityUtils.setUpFsverity(path, (byte[]) null);
+ // Enabling fs-verity is a blocking operation. To reduce the impact to the install time,
+ // run in a background thread.
+ final ArrayList<String> apkPaths = new ArrayList<>();
+ apkPaths.add(pkg.getBaseApkPath());
+ if (pkg.getSplitCodePaths() != null) {
+ Collections.addAll(apkPaths, pkg.getSplitCodePaths());
+ }
+ mInjector.getBackgroundHandler().post(() -> {
+ try {
+ for (String path : apkPaths) {
+ if (!VerityUtils.hasFsverity(path)) {
+ VerityUtils.setUpFsverity(path, (byte[]) null);
+ }
}
+ } catch (IOException e) {
+ // There's nothing we can do if the setup failed. Since fs-verity is
+ // optional, just ignore the error for now.
+ Slog.e(TAG, "Failed to fully enable fs-verity to " + packageName);
}
- } catch (IOException e) {
- // There's nothing we can do if the setup failed. Since fs-verity is
- // optional, just ignore the error for now.
- Slog.e(TAG, "Failed to fully enable fs-verity to " + packageName);
- }
+ });
// Hardcode previousAppId to 0 to disable any data migration (http://b/221088088)
mAppDataHelper.prepareAppDataPostCommitLIF(pkg, 0);
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 409d3524c312..4803c5e873b6 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -154,6 +154,11 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
/** Destroy sessions older than this on storage free request */
private static final long MAX_SESSION_AGE_ON_LOW_STORAGE_MILLIS = 8 * DateUtils.HOUR_IN_MILLIS;
+ /** Threshold of historical sessions size */
+ private static final int HISTORICAL_SESSIONS_THRESHOLD = 500;
+ /** Size of historical sessions to be cleared when reaching threshold */
+ private static final int HISTORICAL_CLEAR_SIZE = 400;
+
/**
* Allow verification-skipping if it's a development app installed through ADB with
* disable verification flag specified.
@@ -549,6 +554,10 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
CharArrayWriter writer = new CharArrayWriter();
IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
session.dump(pw);
+ if (mHistoricalSessions.size() > HISTORICAL_SESSIONS_THRESHOLD) {
+ Slog.d(TAG, "Historical sessions size reaches threshold, clear the oldest");
+ mHistoricalSessions.subList(0, HISTORICAL_CLEAR_SIZE).clear();
+ }
mHistoricalSessions.add(writer.toString());
int installerUid = session.getInstallerUid();
diff --git a/services/core/java/com/android/server/pm/PackageManagerLocal.java b/services/core/java/com/android/server/pm/PackageManagerLocal.java
index 21c2f2c9e330..d163d3d7a085 100644
--- a/services/core/java/com/android/server/pm/PackageManagerLocal.java
+++ b/services/core/java/com/android/server/pm/PackageManagerLocal.java
@@ -30,7 +30,6 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.List;
import java.util.Map;
-import java.util.function.Consumer;
/**
* In-process API for server side PackageManager related infrastructure.
@@ -167,15 +166,15 @@ public interface PackageManagerLocal {
PackageState getPackageState(@NonNull String packageName);
/**
- * Iterates on all states. This should only be used when either the target package name
- * is not known or the large majority of the states are expected to be used.
- *
+ * Returns a map of all {@link PackageState PackageStates} on the device.
+ * <p>
* This will cause app visibility filtering to be invoked on each state on the device,
- * which can be expensive.
+ * which can be expensive. Prefer {@link #getPackageState(String)} if possible.
*
- * @param consumer Block to accept each state as it becomes available post-filtering.
+ * @return Mapping of package name to {@link PackageState}.
*/
- void forAllPackageStates(@NonNull Consumer<PackageState> consumer);
+ @NonNull
+ Map<String, PackageState> getPackageStates();
@Override
void close();
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 9e1bffb7abd5..cf59a1e248e0 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -7317,6 +7317,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService
}
consumer.accept(mPackageStateMutator);
+ mPackageStateMutator.onFinished();
onChanged();
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index 77334e520a91..a72ae56c8c41 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -142,6 +142,11 @@ public class PackageManagerServiceUtils {
public static final Predicate<PackageStateInternal> REMOVE_IF_NULL_PKG =
pkgSetting -> pkgSetting.getPkg() == null;
+ // This is a horrible hack to workaround b/240373119, specifically for fixing the T branch.
+ // A proper fix should be implemented in master instead.
+ public static final ThreadLocal<Boolean> DISABLE_ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS =
+ ThreadLocal.withInitial(() -> false);
+
/**
* Components of apps targeting Android T and above will stop receiving intents from
* external callers that do not match its declared intent filters.
@@ -1093,6 +1098,8 @@ public class PackageManagerServiceUtils {
PlatformCompat compat, ComponentResolverApi resolver,
List<ResolveInfo> resolveInfos, boolean isReceiver,
Intent intent, String resolvedType, int filterCallingUid) {
+ if (DISABLE_ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS.get()) return;
+
final Printer logPrinter = DEBUG_INTENT_MATCHING
? new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM)
: null;
diff --git a/services/core/java/com/android/server/pm/PackageRemovedInfo.java b/services/core/java/com/android/server/pm/PackageRemovedInfo.java
index 4cac1151136d..dd580a5e43aa 100644
--- a/services/core/java/com/android/server/pm/PackageRemovedInfo.java
+++ b/services/core/java/com/android/server/pm/PackageRemovedInfo.java
@@ -115,6 +115,7 @@ final class PackageRemovedInfo {
final int removedUid = mRemovedAppId >= 0 ? mRemovedAppId : mUid;
extras.putInt(Intent.EXTRA_UID, removedUid);
extras.putBoolean(Intent.EXTRA_DATA_REMOVED, mDataRemoved);
+ extras.putBoolean(Intent.EXTRA_SYSTEM_UPDATE_UNINSTALL, mIsRemovedPackageSystemUpdate);
extras.putBoolean(Intent.EXTRA_DONT_KILL_APP, !killApp);
extras.putBoolean(Intent.EXTRA_USER_INITIATED, !removedBySystem);
final boolean isReplace = mIsUpdate || mIsRemovedPackageSystemUpdate;
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 3ec6e7dcdef6..b18179e7b752 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -664,7 +664,9 @@ public class PackageSetting extends SettingBase implements PackageStateInternal
mUserStates.put(other.mUserStates.keyAt(i),
other.mUserStates.valueAt(i).snapshot());
} else {
- mUserStates.put(other.mUserStates.keyAt(i), other.mUserStates.valueAt(i));
+ var userState = other.mUserStates.valueAt(i);
+ userState.setWatchable(this);
+ mUserStates.put(other.mUserStates.keyAt(i), userState);
}
}
@@ -1378,7 +1380,11 @@ public class PackageSetting extends SettingBase implements PackageStateInternal
return mSecondaryCpuAbi;
}
-
+ @ApplicationInfo.HiddenApiEnforcementPolicy
+ @Override
+ public int getHiddenApiEnforcementPolicy() {
+ return AndroidPackageUtils.getHiddenApiEnforcementPolicy(getAndroidPackage(), this);
+ }
// Code below generated by codegen v1.0.23.
//
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 4aba01637abf..89b74f400919 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -367,8 +367,14 @@ public final class Settings implements Watchable, Snappable {
@Watched(manual = true)
private final RuntimePermissionPersistence mRuntimePermissionsPersistence;
+ // Current settings file.
private final File mSettingsFilename;
- private final File mBackupSettingsFilename;
+ // Compressed current settings file.
+ private final File mCompressedSettingsFilename;
+ // Previous settings file.
+ // Removed when the current settings file successfully stored.
+ private final File mPreviousSettingsFilename;
+
private final File mPackageListFilename;
private final File mStoppedPackagesFilename;
private final File mBackupStoppedPackagesFilename;
@@ -635,7 +641,8 @@ public final class Settings implements Watchable, Snappable {
mRuntimePermissionsPersistence = null;
mPermissionDataProvider = null;
mSettingsFilename = null;
- mBackupSettingsFilename = null;
+ mCompressedSettingsFilename = null;
+ mPreviousSettingsFilename = null;
mPackageListFilename = null;
mStoppedPackagesFilename = null;
mBackupStoppedPackagesFilename = null;
@@ -706,7 +713,8 @@ public final class Settings implements Watchable, Snappable {
|FileUtils.S_IROTH|FileUtils.S_IXOTH,
-1, -1);
mSettingsFilename = new File(mSystemDir, "packages.xml");
- mBackupSettingsFilename = new File(mSystemDir, "packages-backup.xml");
+ mCompressedSettingsFilename = new File(mSystemDir, "packages.compressed");
+ mPreviousSettingsFilename = new File(mSystemDir, "packages-backup.xml");
mPackageListFilename = new File(mSystemDir, "packages.list");
FileUtils.setPermissions(mPackageListFilename, 0640, SYSTEM_UID, PACKAGE_INFO_GID);
@@ -747,7 +755,8 @@ public final class Settings implements Watchable, Snappable {
mLock = null;
mRuntimePermissionsPersistence = r.mRuntimePermissionsPersistence;
mSettingsFilename = null;
- mBackupSettingsFilename = null;
+ mCompressedSettingsFilename = null;
+ mPreviousSettingsFilename = null;
mPackageListFilename = null;
mStoppedPackagesFilename = null;
mBackupStoppedPackagesFilename = null;
@@ -2461,7 +2470,7 @@ public final class Settings implements Watchable, Snappable {
mReadMessages.append("Reading from backup stopped packages file\n");
PackageManagerService.reportSettingsProblem(Log.INFO,
"Need to read from backup stopped packages file");
- if (mSettingsFilename.exists()) {
+ if (mStoppedPackagesFilename.exists()) {
// If both the backup and normal file exist, we
// ignore the normal one since it might have been
// corrupted.
@@ -2572,10 +2581,10 @@ public final class Settings implements Watchable, Snappable {
// to persist settings earlier. So preserve the older
// backup for future reference since the current settings
// might have been corrupted.
- if (!mBackupSettingsFilename.exists()) {
- if (!mSettingsFilename.renameTo(mBackupSettingsFilename)) {
+ if (!mPreviousSettingsFilename.exists()) {
+ if (!mSettingsFilename.renameTo(mPreviousSettingsFilename)) {
Slog.wtf(PackageManagerService.TAG,
- "Unable to backup package manager settings, "
+ "Unable to store older package manager settings, "
+ " current changes will be lost at reboot");
return;
}
@@ -2584,6 +2593,8 @@ public final class Settings implements Watchable, Snappable {
Slog.w(PackageManagerService.TAG, "Preserving older settings backup");
}
}
+ // Compressed settings are not valid anymore.
+ mCompressedSettingsFilename.delete();
mPastSignatures.clear();
@@ -2669,14 +2680,34 @@ public final class Settings implements Watchable, Snappable {
FileUtils.sync(fstr);
fstr.close();
- // New settings successfully written, old ones are no longer
- // needed.
- mBackupSettingsFilename.delete();
+ // New settings successfully written, old ones are no longer needed.
+ mPreviousSettingsFilename.delete();
+
FileUtils.setPermissions(mSettingsFilename.toString(),
- FileUtils.S_IRUSR|FileUtils.S_IWUSR
- |FileUtils.S_IRGRP|FileUtils.S_IWGRP,
+ FileUtils.S_IRUSR | FileUtils.S_IWUSR | FileUtils.S_IRGRP | FileUtils.S_IWGRP,
-1, -1);
+ final FileInputStream fis = new FileInputStream(mSettingsFilename);
+ final AtomicFile compressed = new AtomicFile(mCompressedSettingsFilename);
+ final FileOutputStream fos = compressed.startWrite();
+
+ BackgroundThread.getHandler().post(() -> {
+ try {
+ if (!nativeCompressLz4(fis.getFD().getInt$(), fos.getFD().getInt$())) {
+ throw new IOException("Failed to compress");
+ }
+ compressed.finishWrite(fos);
+ FileUtils.setPermissions(mCompressedSettingsFilename.toString(),
+ FileUtils.S_IRUSR | FileUtils.S_IWUSR | FileUtils.S_IRGRP
+ | FileUtils.S_IWGRP, -1, -1);
+ } catch (IOException e) {
+ Slog.e(PackageManagerService.TAG, "Failed to write compressed settings file: "
+ + mCompressedSettingsFilename, e);
+ compressed.delete();
+ }
+ IoUtils.closeQuietly(fis);
+ });
+
writeKernelMappingLPr();
writePackageListLPr();
writeAllUsersPackageRestrictionsLPr(sync);
@@ -2699,6 +2730,8 @@ public final class Settings implements Watchable, Snappable {
//Debug.stopMethodTracing();
}
+ private native boolean nativeCompressLz4(int inputFd, int outputFd);
+
private void writeKernelRemoveUserLPr(int userId) {
if (mKernelMappingFilename == null) return;
@@ -3109,16 +3142,15 @@ public final class Settings implements Watchable, Snappable {
boolean readLPw(@NonNull Computer computer, @NonNull List<UserInfo> users) {
FileInputStream str = null;
- if (mBackupSettingsFilename.exists()) {
+ if (mPreviousSettingsFilename.exists()) {
try {
- str = new FileInputStream(mBackupSettingsFilename);
+ str = new FileInputStream(mPreviousSettingsFilename);
mReadMessages.append("Reading from backup settings file\n");
PackageManagerService.reportSettingsProblem(Log.INFO,
"Need to read from backup settings file");
if (mSettingsFilename.exists()) {
- // If both the backup and settings file exist, we
- // ignore the settings since it might have been
- // corrupted.
+ // If both the previous and current settings files exist,
+ // we ignore the current since it might have been corrupted.
Slog.w(PackageManagerService.TAG, "Cleaning up settings file "
+ mSettingsFilename);
mSettingsFilename.delete();
@@ -5594,8 +5626,8 @@ public final class Settings implements Watchable, Snappable {
}
private static final class RuntimePermissionPersistence {
- // 200-400ms delay to avoid monopolizing PMS lock when written for multiple users.
- private static final long WRITE_PERMISSIONS_DELAY_MILLIS = 300;
+ // 700-1300ms delay to avoid monopolizing PMS lock when written for multiple users.
+ private static final long WRITE_PERMISSIONS_DELAY_MILLIS = 1000;
private static final double WRITE_PERMISSIONS_DELAY_JITTER = 0.3;
private static final long MAX_WRITE_PERMISSIONS_DELAY_MILLIS = 2000;
@@ -5613,8 +5645,7 @@ public final class Settings implements Watchable, Snappable {
// Low-priority handlers running on SystemBg thread.
private final Handler mAsyncHandler = new MyHandler();
- private final Handler mPersistenceHandler = new Handler(
- BackgroundThread.getHandler().getLooper());
+ private final Handler mPersistenceHandler = new PersistenceHandler();
private final Object mLock = new Object();
@@ -5761,20 +5792,22 @@ public final class Settings implements Watchable, Snappable {
@NonNull WatchedArrayMap<String, SharedUserSetting> sharedUsers,
@Nullable Handler pmHandler, @NonNull Object pmLock,
boolean sync) {
- final int version;
- final String fingerprint;
- final boolean isLegacyPermissionStateStale;
synchronized (mLock) {
mAsyncHandler.removeMessages(userId);
mWriteScheduled.delete(userId);
-
- version = mVersions.get(userId, INITIAL_VERSION);
- fingerprint = mFingerprints.get(userId);
- isLegacyPermissionStateStale = mIsLegacyPermissionStateStale;
- mIsLegacyPermissionStateStale = false;
}
Runnable writer = () -> {
+ final int version;
+ final String fingerprint;
+ final boolean isLegacyPermissionStateStale;
+ synchronized (mLock) {
+ version = mVersions.get(userId, INITIAL_VERSION);
+ fingerprint = mFingerprints.get(userId);
+ isLegacyPermissionStateStale = mIsLegacyPermissionStateStale;
+ mIsLegacyPermissionStateStale = false;
+ }
+
final RuntimePermissionsState runtimePermissions;
synchronized (pmLock) {
if (sync || isLegacyPermissionStateStale) {
@@ -5823,7 +5856,7 @@ public final class Settings implements Watchable, Snappable {
}
if (pmHandler != null) {
// Async version.
- mPersistenceHandler.post(() -> writePendingStates());
+ mPersistenceHandler.obtainMessage(userId).sendToTarget();
} else {
// Sync version.
writePendingStates();
@@ -6099,6 +6132,17 @@ public final class Settings implements Watchable, Snappable {
}
}
}
+
+ private final class PersistenceHandler extends Handler {
+ PersistenceHandler() {
+ super(BackgroundThread.getHandler().getLooper());
+ }
+
+ @Override
+ public void handleMessage(Message message) {
+ writePendingStates();
+ }
+ }
}
/**
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index 0362dddf09a8..4fddc9c17b62 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -1470,9 +1470,15 @@ class ShortcutPackage extends ShortcutPackageItem {
}
// Then make sure none of the activities have more than the max number of shortcuts.
+ int total = 0;
for (int i = counts.size() - 1; i >= 0; i--) {
- service.enforceMaxActivityShortcuts(counts.valueAt(i));
+ int count = counts.valueAt(i);
+ service.enforceMaxActivityShortcuts(count);
+ total += count;
}
+
+ // Finally make sure that the app doesn't have more than the max number of shortcuts.
+ service.enforceMaxAppShortcuts(total);
}
/**
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 83720f1445fa..12a33ee488bb 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -181,6 +181,9 @@ public class ShortcutService extends IShortcutService.Stub {
static final int DEFAULT_MAX_SHORTCUTS_PER_ACTIVITY = 15;
@VisibleForTesting
+ static final int DEFAULT_MAX_SHORTCUTS_PER_APP = 60;
+
+ @VisibleForTesting
static final int DEFAULT_MAX_ICON_DIMENSION_DP = 96;
@VisibleForTesting
@@ -257,6 +260,11 @@ public class ShortcutService extends IShortcutService.Stub {
String KEY_MAX_SHORTCUTS = "max_shortcuts";
/**
+ * Key name for the max dynamic shortcuts per app. (int)
+ */
+ String KEY_MAX_SHORTCUTS_PER_APP = "max_shortcuts_per_app";
+
+ /**
* Key name for icon compression quality, 0-100.
*/
String KEY_ICON_QUALITY = "icon_quality";
@@ -329,9 +337,14 @@ public class ShortcutService extends IShortcutService.Stub {
new SparseArray<>();
/**
+ * Max number of dynamic + manifest shortcuts that each activity can have at a time.
+ */
+ private int mMaxShortcutsPerActivity;
+
+ /**
* Max number of dynamic + manifest shortcuts that each application can have at a time.
*/
- private int mMaxShortcuts;
+ private int mMaxShortcutsPerApp;
/**
* Max number of updating API calls that each application can make during the interval.
@@ -804,9 +817,12 @@ public class ShortcutService extends IShortcutService.Stub {
mMaxUpdatesPerInterval = Math.max(0, (int) parser.getLong(
ConfigConstants.KEY_MAX_UPDATES_PER_INTERVAL, DEFAULT_MAX_UPDATES_PER_INTERVAL));
- mMaxShortcuts = Math.max(0, (int) parser.getLong(
+ mMaxShortcutsPerActivity = Math.max(0, (int) parser.getLong(
ConfigConstants.KEY_MAX_SHORTCUTS, DEFAULT_MAX_SHORTCUTS_PER_ACTIVITY));
+ mMaxShortcutsPerApp = Math.max(0, (int) parser.getLong(
+ ConfigConstants.KEY_MAX_SHORTCUTS_PER_APP, DEFAULT_MAX_SHORTCUTS_PER_APP));
+
final int iconDimensionDp = Math.max(1, injectIsLowRamDevice()
? (int) parser.getLong(
ConfigConstants.KEY_MAX_ICON_DIMENSION_DP_LOWRAM,
@@ -1746,16 +1762,33 @@ public class ShortcutService extends IShortcutService.Stub {
* {@link #getMaxActivityShortcuts()}.
*/
void enforceMaxActivityShortcuts(int numShortcuts) {
- if (numShortcuts > mMaxShortcuts) {
+ if (numShortcuts > mMaxShortcutsPerActivity) {
throw new IllegalArgumentException("Max number of dynamic shortcuts exceeded");
}
}
/**
+ * @throws IllegalArgumentException if {@code numShortcuts} is bigger than
+ * {@link #getMaxAppShortcuts()}.
+ */
+ void enforceMaxAppShortcuts(int numShortcuts) {
+ if (numShortcuts > mMaxShortcutsPerApp) {
+ throw new IllegalArgumentException("Max number of dynamic shortcuts per app exceeded");
+ }
+ }
+
+ /**
* Return the max number of dynamic + manifest shortcuts for each launcher icon.
*/
int getMaxActivityShortcuts() {
- return mMaxShortcuts;
+ return mMaxShortcutsPerActivity;
+ }
+
+ /**
+ * Return the max number of dynamic + manifest shortcuts for each launcher icon.
+ */
+ int getMaxAppShortcuts() {
+ return mMaxShortcutsPerApp;
}
/**
@@ -2188,6 +2221,8 @@ public class ShortcutService extends IShortcutService.Stub {
ps.ensureNotImmutable(shortcut.getId(), /*ignoreInvisible=*/ true);
fillInDefaultActivity(Arrays.asList(shortcut));
+ enforceMaxAppShortcuts(ps.getShortcutCount());
+
if (!shortcut.hasRank()) {
shortcut.setRank(0);
}
@@ -2575,7 +2610,7 @@ public class ShortcutService extends IShortcutService.Stub {
throws RemoteException {
verifyCaller(packageName, userId);
- return mMaxShortcuts;
+ return mMaxShortcutsPerActivity;
}
@Override
@@ -4724,7 +4759,7 @@ public class ShortcutService extends IShortcutService.Stub {
pw.print(" maxUpdatesPerInterval: ");
pw.println(mMaxUpdatesPerInterval);
pw.print(" maxShortcutsPerActivity: ");
- pw.println(mMaxShortcuts);
+ pw.println(mMaxShortcutsPerActivity);
pw.println();
mStatLogger.dump(pw, " ");
@@ -5211,7 +5246,7 @@ public class ShortcutService extends IShortcutService.Stub {
@VisibleForTesting
int getMaxShortcutsForTest() {
- return mMaxShortcuts;
+ return mMaxShortcutsPerActivity;
}
@VisibleForTesting
diff --git a/services/core/java/com/android/server/pm/UserManagerInternal.java b/services/core/java/com/android/server/pm/UserManagerInternal.java
index 0f920c6a4e90..5c8992691034 100644
--- a/services/core/java/com/android/server/pm/UserManagerInternal.java
+++ b/services/core/java/com/android/server/pm/UserManagerInternal.java
@@ -141,23 +141,39 @@ public abstract class UserManagerInternal {
/**
* Called by {@link com.android.server.devicepolicy.DevicePolicyManagerService} to update
* whether the device is managed by device owner.
+ *
+ * @deprecated Use methods in {@link android.app.admin.DevicePolicyManagerInternal}.
*/
+ @Deprecated
+ // TODO(b/258213147): Remove
public abstract void setDeviceManaged(boolean isManaged);
/**
* Returns whether the device is managed by device owner.
+ *
+ * @deprecated Use methods in {@link android.app.admin.DevicePolicyManagerInternal}.
*/
+ @Deprecated
+ // TODO(b/258213147): Remove
public abstract boolean isDeviceManaged();
/**
* Called by {@link com.android.server.devicepolicy.DevicePolicyManagerService} to update
* whether the user is managed by profile owner.
+ *
+ * @deprecated Use methods in {@link android.app.admin.DevicePolicyManagerInternal}.
*/
+ // TODO(b/258213147): Remove
+ @Deprecated
public abstract void setUserManaged(int userId, boolean isManaged);
/**
- * whether a profile owner manages this user.
+ * Whether a profile owner manages this user.
+ *
+ * @deprecated Use methods in {@link android.app.admin.DevicePolicyManagerInternal}.
*/
+ // TODO(b/258213147): Remove
+ @Deprecated
public abstract boolean isUserManaged(int userId);
/**
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 3234e87a7125..9a6e5d41655a 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -93,6 +93,7 @@ import android.os.storage.StorageManagerInternal;
import android.provider.Settings;
import android.service.voice.VoiceInteractionManagerInternal;
import android.stats.devicepolicy.DevicePolicyEnums;
+import android.telecom.TelecomManager;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -128,7 +129,6 @@ import com.android.server.SystemService;
import com.android.server.am.UserState;
import com.android.server.pm.UserManagerInternal.UserLifecycleListener;
import com.android.server.pm.UserManagerInternal.UserRestrictionsListener;
-import com.android.server.pm.UserManagerInternal.UserVisibilityListener;
import com.android.server.storage.DeviceStorageMonitorInternal;
import com.android.server.utils.Slogf;
import com.android.server.utils.TimingsTraceAndSlog;
@@ -1970,6 +1970,63 @@ public class UserManagerService extends IUserManager.Stub {
}
}
+ /**
+ * Returns whether switching users is currently allowed for the provided user.
+ * <p>
+ * Switching users is not allowed in the following cases:
+ * <li>the user is in a phone call</li>
+ * <li>{@link UserManager#DISALLOW_USER_SWITCH} is set</li>
+ * <li>system user hasn't been unlocked yet</li>
+ *
+ * @return A {@link UserManager.UserSwitchabilityResult} flag indicating if the user is
+ * switchable.
+ */
+ public @UserManager.UserSwitchabilityResult int getUserSwitchability(int userId) {
+ checkManageOrInteractPermissionIfCallerInOtherProfileGroup(userId, "getUserSwitchability");
+
+ final TimingsTraceAndSlog t = new TimingsTraceAndSlog();
+ t.traceBegin("getUserSwitchability-" + userId);
+
+ int flags = UserManager.SWITCHABILITY_STATUS_OK;
+
+ t.traceBegin("TM.isInCall");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ final TelecomManager telecomManager = mContext.getSystemService(TelecomManager.class);
+ if (telecomManager != null && telecomManager.isInCall()) {
+ flags |= UserManager.SWITCHABILITY_STATUS_USER_IN_CALL;
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ t.traceEnd();
+
+ t.traceBegin("hasUserRestriction-DISALLOW_USER_SWITCH");
+ if (mLocalService.hasUserRestriction(DISALLOW_USER_SWITCH, userId)) {
+ flags |= UserManager.SWITCHABILITY_STATUS_USER_SWITCH_DISALLOWED;
+ }
+ t.traceEnd();
+
+ // System User is always unlocked in Headless System User Mode, so ignore this flag
+ if (!isHeadlessSystemUserMode()) {
+ t.traceBegin("getInt-ALLOW_USER_SWITCHING_WHEN_SYSTEM_USER_LOCKED");
+ final boolean allowUserSwitchingWhenSystemUserLocked = Settings.Global.getInt(
+ mContext.getContentResolver(),
+ Settings.Global.ALLOW_USER_SWITCHING_WHEN_SYSTEM_USER_LOCKED, 0) != 0;
+ t.traceEnd();
+ t.traceBegin("isUserUnlocked-USER_SYSTEM");
+ final boolean systemUserUnlocked = mLocalService.isUserUnlocked(UserHandle.USER_SYSTEM);
+ t.traceEnd();
+
+ if (!allowUserSwitchingWhenSystemUserLocked && !systemUserUnlocked) {
+ flags |= UserManager.SWITCHABILITY_STATUS_SYSTEM_USER_LOCKED;
+ }
+ }
+ t.traceEnd();
+
+ return flags;
+ }
+
@VisibleForTesting
boolean isUserSwitcherEnabled(@UserIdInt int mUserId) {
boolean multiUserSettingOn = Settings.Global.getInt(mContext.getContentResolver(),
@@ -6602,6 +6659,7 @@ public class UserManagerService extends IUserManager.Stub {
}
}
+ // TODO(b/258213147): Remove
@Override
public void setDeviceManaged(boolean isManaged) {
synchronized (mUsersLock) {
@@ -6609,6 +6667,7 @@ public class UserManagerService extends IUserManager.Stub {
}
}
+ // TODO(b/258213147): Remove
@Override
public boolean isDeviceManaged() {
synchronized (mUsersLock) {
@@ -6616,6 +6675,7 @@ public class UserManagerService extends IUserManager.Stub {
}
}
+ // TODO(b/258213147): Remove
@Override
public void setUserManaged(@UserIdInt int userId, boolean isManaged) {
synchronized (mUsersLock) {
@@ -6623,6 +6683,7 @@ public class UserManagerService extends IUserManager.Stub {
}
}
+ // TODO(b/258213147): Remove
@Override
public boolean isUserManaged(@UserIdInt int userId) {
synchronized (mUsersLock) {
diff --git a/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java b/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java
index f388e070bb85..f0bf1ea80570 100644
--- a/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java
+++ b/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java
@@ -298,7 +298,9 @@ public class ArtStatsLogUtils {
dexMetadataType,
apkType,
ISA_MAP.getOrDefault(isa,
- ArtStatsLog.ART_DATUM_REPORTED__ISA__ART_ISA_UNKNOWN));
+ ArtStatsLog.ART_DATUM_REPORTED__ISA__ART_ISA_UNKNOWN),
+ ArtStatsLog.ART_DATUM_REPORTED__GC__ART_GC_COLLECTOR_TYPE_UNKNOWN,
+ ArtStatsLog.ART_DATUM_REPORTED__UFFD_SUPPORT__ART_UFFD_SUPPORT_UNKNOWN);
}
}
diff --git a/services/core/java/com/android/server/pm/local/PackageManagerLocalImpl.java b/services/core/java/com/android/server/pm/local/PackageManagerLocalImpl.java
index 4ff0d59aba7f..f8e154755cff 100644
--- a/services/core/java/com/android/server/pm/local/PackageManagerLocalImpl.java
+++ b/services/core/java/com/android/server/pm/local/PackageManagerLocalImpl.java
@@ -22,6 +22,7 @@ import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.os.Binder;
import android.os.UserHandle;
+import android.util.ArrayMap;
import com.android.server.pm.Computer;
import com.android.server.pm.PackageManagerLocal;
@@ -30,11 +31,9 @@ import com.android.server.pm.pkg.PackageState;
import com.android.server.pm.snapshot.PackageDataSnapshot;
import java.io.IOException;
-import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
-import java.util.function.Consumer;
/** @hide */
public class PackageManagerLocalImpl implements PackageManagerLocal {
@@ -143,7 +142,7 @@ public class PackageManagerLocalImpl implements PackageManagerLocal {
private final int mUserId;
@Nullable
- private ArrayList<PackageState> mFilteredPackageStates;
+ private Map<String, PackageState> mFilteredPackageStates;
@Nullable
private final UnfilteredSnapshotImpl mParentSnapshot;
@@ -179,26 +178,24 @@ public class PackageManagerLocalImpl implements PackageManagerLocal {
return mSnapshot.getPackageStateFiltered(packageName, mCallingUid, mUserId);
}
+ @NonNull
@Override
- public void forAllPackageStates(@NonNull Consumer<PackageState> consumer) {
+ public Map<String, PackageState> getPackageStates() {
checkClosed();
if (mFilteredPackageStates == null) {
var packageStates = mSnapshot.getPackageStates();
- var filteredPackageStates = new ArrayList<PackageState>();
+ var filteredPackageStates = new ArrayMap<String, PackageState>();
for (int index = 0, size = packageStates.size(); index < size; index++) {
var packageState = packageStates.valueAt(index);
if (!mSnapshot.shouldFilterApplication(packageState, mCallingUid, mUserId)) {
- filteredPackageStates.add(packageState);
+ filteredPackageStates.put(packageStates.keyAt(index), packageState);
}
}
- mFilteredPackageStates = filteredPackageStates;
+ mFilteredPackageStates = Collections.unmodifiableMap(filteredPackageStates);
}
- for (int index = 0, size = mFilteredPackageStates.size(); index < size; index++) {
- var packageState = mFilteredPackageStates.get(index);
- consumer.accept(packageState);
- }
+ return mFilteredPackageStates;
}
}
}
diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
index 558202b99de2..a3fa25d2c03d 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
@@ -533,7 +533,7 @@ public class PackageInfoUtils {
ai.metaData = null;
}
ai.applicationInfo = applicationInfo;
- ai.targetDisplayCategory = a.getTargetDisplayCategory();
+ ai.requiredDisplayCategory = a.getRequiredDisplayCategory();
ai.setKnownActivityEmbeddingCerts(a.getKnownActivityEmbeddingCerts());
assignFieldsComponentInfoParsedMainComponent(ai, a, pkgSetting, userId);
return ai;
diff --git a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
index c76b12983656..82b5fa27af2c 100644
--- a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
@@ -234,10 +234,12 @@ public class AndroidPackageUtils {
|| !pkg.getLibraryNames().isEmpty();
}
- public static int getHiddenApiEnforcementPolicy(AndroidPackage pkg,
+ public static int getHiddenApiEnforcementPolicy(@Nullable AndroidPackage pkg,
@NonNull PackageStateInternal pkgSetting) {
boolean isAllowedToUseHiddenApis;
- if (pkg.isSignedWithPlatformKey()) {
+ if (pkg == null) {
+ isAllowedToUseHiddenApis = false;
+ } else if (pkg.isSignedWithPlatformKey()) {
isAllowedToUseHiddenApis = true;
} else if (pkg.isSystem() || pkgSetting.getTransientState().isUpdatedSystemApp()) {
isAllowedToUseHiddenApis = pkg.isUsesNonSdkApi()
diff --git a/services/core/java/com/android/server/pm/permission/Permission.java b/services/core/java/com/android/server/pm/permission/Permission.java
index 69e7bf119304..165c52d990ee 100644
--- a/services/core/java/com/android/server/pm/permission/Permission.java
+++ b/services/core/java/com/android/server/pm/permission/Permission.java
@@ -322,6 +322,10 @@ public final class Permission {
return (mPermissionInfo.protectionLevel & PermissionInfo.PROTECTION_FLAG_COMPANION) != 0;
}
+ public boolean isModule() {
+ return (mPermissionInfo.protectionLevel & PermissionInfo.PROTECTION_FLAG_MODULE) != 0;
+ }
+
public boolean isRetailDemo() {
return (mPermissionInfo.protectionLevel & PermissionInfo.PROTECTION_FLAG_RETAIL_DEMO) != 0;
}
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 9ec63fcf7125..cefe9cdec6c7 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -467,7 +467,7 @@ public class PermissionManagerService extends IPermissionManager.Stub {
@Override
public PermissionInfo getPermissionInfo(String permissionName, String packageName, int flags) {
- return mPermissionManagerServiceImpl.getPermissionInfo(permissionName, packageName, flags);
+ return mPermissionManagerServiceImpl.getPermissionInfo(permissionName, flags, packageName);
}
@Override
@@ -792,14 +792,14 @@ public class PermissionManagerService extends IPermissionManager.Stub {
@NonNull
@Override
- public ArrayList<PermissionInfo> getAllPermissionsWithProtection(
+ public List<PermissionInfo> getAllPermissionsWithProtection(
@PermissionInfo.Protection int protection) {
return mPermissionManagerServiceImpl.getAllPermissionsWithProtection(protection);
}
@NonNull
@Override
- public ArrayList<PermissionInfo> getAllPermissionsWithProtectionFlags(
+ public List<PermissionInfo> getAllPermissionsWithProtectionFlags(
@PermissionInfo.ProtectionFlags int protectionFlags) {
return mPermissionManagerServiceImpl
.getAllPermissionsWithProtectionFlags(protectionFlags);
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
index 5ffbbdcfbb0b..2a2bcab56258 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
@@ -238,6 +238,8 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt
NOTIFICATION_PERMISSIONS.add(Manifest.permission.POST_NOTIFICATIONS);
}
+ @NonNull private final ApexManager mApexManager;
+
/** Set of source package names for Privileged Permission Allowlist */
private final ArraySet<String> mPrivilegedPermissionAllowlistSourcePackageNames =
new ArraySet<>();
@@ -421,6 +423,7 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt
mPackageManagerInt = LocalServices.getService(PackageManagerInternal.class);
mUserManagerInt = LocalServices.getService(UserManagerInternal.class);
mIsLeanback = availableFeatures.containsKey(PackageManager.FEATURE_LEANBACK);
+ mApexManager = ApexManager.getInstance();
mPrivilegedPermissionAllowlistSourcePackageNames.add(PLATFORM_PACKAGE_NAME);
// PackageManager.hasSystemFeature() is not used here because PackageManagerService
@@ -559,8 +562,8 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt
@Override
@Nullable
- public PermissionInfo getPermissionInfo(@NonNull String permName, @NonNull String opPackageName,
- @PackageManager.PermissionInfoFlags int flags) {
+ public PermissionInfo getPermissionInfo(@NonNull String permName,
+ @PackageManager.PermissionInfoFlags int flags, @NonNull String opPackageName) {
final int callingUid = Binder.getCallingUid();
if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) {
return null;
@@ -2127,7 +2130,7 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt
for (int i = 0; i < numRequestedPermissions; i++) {
PermissionInfo permInfo = getPermissionInfo(
newPackage.getRequestedPermissions().get(i),
- newPackage.getPackageName(), 0);
+ 0, newPackage.getPackageName());
if (permInfo == null) {
continue;
}
@@ -3309,9 +3312,8 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt
return true;
}
final String permissionName = permission.getName();
- final ApexManager apexManager = ApexManager.getInstance();
final String containingApexPackageName =
- apexManager.getActiveApexPackageNameContainingPackage(packageName);
+ mApexManager.getActiveApexPackageNameContainingPackage(packageName);
if (isInSystemConfigPrivAppPermissions(pkg, permissionName,
containingApexPackageName)) {
return true;
@@ -3365,8 +3367,7 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt
} else if (pkg.isSystemExt()) {
permissions = systemConfig.getSystemExtPrivAppPermissions(pkg.getPackageName());
} else if (containingApexPackageName != null) {
- final ApexManager apexManager = ApexManager.getInstance();
- final String apexName = apexManager.getApexModuleNameForPackageName(
+ final String apexName = mApexManager.getApexModuleNameForPackageName(
containingApexPackageName);
final Set<String> privAppPermissions = systemConfig.getPrivAppPermissions(
pkg.getPackageName());
@@ -3582,6 +3583,11 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt
// Special permission for the recents app.
allowed = true;
}
+ if (!allowed && bp.isModule() && mApexManager.getActiveApexPackageNameContainingPackage(
+ pkg.getPackageName()) != null) {
+ // Special permission granted for APKs inside APEX modules.
+ allowed = true;
+ }
return allowed;
}
@@ -5204,9 +5210,9 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt
@NonNull
@Override
- public ArrayList<PermissionInfo> getAllPermissionsWithProtection(
+ public List<PermissionInfo> getAllPermissionsWithProtection(
@PermissionInfo.Protection int protection) {
- ArrayList<PermissionInfo> matchingPermissions = new ArrayList<>();
+ List<PermissionInfo> matchingPermissions = new ArrayList<>();
synchronized (mLock) {
for (final Permission permission : mRegistry.getPermissions()) {
@@ -5221,9 +5227,9 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt
@NonNull
@Override
- public ArrayList<PermissionInfo> getAllPermissionsWithProtectionFlags(
+ public List<PermissionInfo> getAllPermissionsWithProtectionFlags(
@PermissionInfo.ProtectionFlags int protectionFlags) {
- ArrayList<PermissionInfo> matchingPermissions = new ArrayList<>();
+ List<PermissionInfo> matchingPermissions = new ArrayList<>();
synchronized (mLock) {
for (final Permission permission : mRegistry.getPermissions()) {
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java
index 930936be9ec6..d9caec7089be 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java
@@ -32,7 +32,6 @@ import com.android.server.pm.pkg.AndroidPackage;
import java.io.FileDescriptor;
import java.io.PrintWriter;
-import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -77,8 +76,8 @@ public interface PermissionManagerServiceInterface extends PermissionManagerInte
* @return a {@link PermissionInfo} containing information about the permission, or {@code null}
* if not found
*/
- PermissionInfo getPermissionInfo(@NonNull String permName, @NonNull String opPackageName,
- @PackageManager.PermissionInfoFlags int flags);
+ PermissionInfo getPermissionInfo(@NonNull String permName,
+ @PackageManager.PermissionInfoFlags int flags, @NonNull String opPackageName);
/**
* Query for all of the permissions associated with a particular group.
@@ -487,11 +486,11 @@ public interface PermissionManagerServiceInterface extends PermissionManagerInte
/** Get all permissions that have a certain protection */
@NonNull
- ArrayList<PermissionInfo> getAllPermissionsWithProtection(
+ List<PermissionInfo> getAllPermissionsWithProtection(
@PermissionInfo.Protection int protection);
/** Get all permissions that have certain protection flags */
- @NonNull ArrayList<PermissionInfo> getAllPermissionsWithProtectionFlags(
+ @NonNull List<PermissionInfo> getAllPermissionsWithProtectionFlags(
@PermissionInfo.ProtectionFlags int protectionFlags);
/**
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 f20620ed6eec..97ac749781d0 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
@@ -164,11 +164,12 @@ public interface PermissionManagerServiceInternal extends PermissionManagerInter
/** Get all permissions that have a certain protection */
@NonNull
- ArrayList<PermissionInfo> getAllPermissionsWithProtection(
+ List<PermissionInfo> getAllPermissionsWithProtection(
@PermissionInfo.Protection int protection);
- /** Get all permissions that have certain protection flags */
- @NonNull ArrayList<PermissionInfo> getAllPermissionsWithProtectionFlags(
+ /** Get all permissions that have certain protection flags
+ * @return*/
+ @NonNull List<PermissionInfo> getAllPermissionsWithProtectionFlags(
@PermissionInfo.ProtectionFlags int protectionFlags);
/**
diff --git a/services/core/java/com/android/server/pm/pkg/PackageState.java b/services/core/java/com/android/server/pm/pkg/PackageState.java
index e8d0640a675c..67b7647db7c1 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageState.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageState.java
@@ -112,6 +112,19 @@ public interface PackageState {
int getAppId();
/**
+ * Retrieves effective hidden API policy for this app. The state can be dependent on
+ * {@link #getAndroidPackage()} availability and whether the app is a system app.
+ *
+ * Note that during process start, this policy may be mutated by device specific process
+ * configuration, so this value isn't truly final.
+ *
+ * @return The (mostly) final {@link ApplicationInfo.HiddenApiEnforcementPolicy} that should be
+ * applied to this package.
+ */
+ @ApplicationInfo.HiddenApiEnforcementPolicy
+ int getHiddenApiEnforcementPolicy();
+
+ /**
* @see PackageInfo#packageName
* @see AndroidPackage#getPackageName()
*/
@@ -139,6 +152,18 @@ public interface PackageState {
String getSeInfo();
/**
+ * @return State for a user or {@link PackageUserState#DEFAULT} if the state doesn't exist.
+ */
+ @NonNull
+ PackageUserState getStateForUser(@NonNull UserHandle user);
+
+ /**
+ * @see R.styleable#AndroidManifestUsesLibrary
+ */
+ @NonNull
+ List<SharedLibrary> getUsesLibraries();
+
+ /**
* @see AndroidPackage#isPrivileged()
*/
boolean isPrivileged();
@@ -154,18 +179,6 @@ public interface PackageState {
*/
boolean isUpdatedSystemApp();
- /**
- * @return State for a user or {@link PackageUserState#DEFAULT} if the state doesn't exist.
- */
- @NonNull
- PackageUserState getStateForUser(@NonNull UserHandle user);
-
- /**
- * @see R.styleable#AndroidManifestUsesLibrary
- */
- @NonNull
- List<SharedLibrary> getUsesLibraries();
-
// Methods below this comment are not yet exposed as API
/**
diff --git a/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java b/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java
index e552a34be70d..43d019a6dbff 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java
@@ -19,6 +19,7 @@ package com.android.server.pm.pkg;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.SigningInfo;
@@ -118,6 +119,8 @@ public class PackageStateImpl implements PackageState {
private final int mCategoryOverride;
@Nullable
private final String mCpuAbiOverride;
+ @ApplicationInfo.HiddenApiEnforcementPolicy
+ private final int mHiddenApiEnforcementPolicy;
private final long mLastModifiedTime;
private final long mLastUpdateTime;
private final long mLongVersionCode;
@@ -170,6 +173,7 @@ public class PackageStateImpl implements PackageState {
mAppId = pkgState.getAppId();
mCategoryOverride = pkgState.getCategoryOverride();
mCpuAbiOverride = pkgState.getCpuAbiOverride();
+ mHiddenApiEnforcementPolicy = pkgState.getHiddenApiEnforcementPolicy();
mLastModifiedTime = pkgState.getLastModifiedTime();
mLastUpdateTime = pkgState.getLastUpdateTime();
mLongVersionCode = pkgState.getVersionCode();
@@ -545,7 +549,7 @@ public class PackageStateImpl implements PackageState {
}
@DataClass.Generated(
- time = 1665778832625L,
+ time = 1666719622708L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java",
inputSignatures = "private int mBooleans\nprivate final long mCeDataInode\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mDisabledComponents\nprivate final @android.content.pm.PackageManager.DistractionRestriction int mDistractionFlags\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mEnabledComponents\nprivate final int mEnabledState\nprivate final @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate final @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate final @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate final @android.annotation.NonNull android.content.pm.overlay.OverlayPaths mOverlayPaths\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate final @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate final @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate final long mFirstInstallTime\npublic static com.android.server.pm.pkg.PackageUserState copy(com.android.server.pm.pkg.PackageUserState)\nprivate void setBoolean(int,boolean)\nprivate boolean getBoolean(int)\npublic @java.lang.Override boolean isHidden()\npublic @java.lang.Override boolean isInstalled()\npublic @java.lang.Override boolean isInstantApp()\npublic @java.lang.Override boolean isNotLaunched()\npublic @java.lang.Override boolean isStopped()\npublic @java.lang.Override boolean isSuspended()\npublic @java.lang.Override boolean isVirtualPreload()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\nclass UserStateImpl extends java.lang.Object implements [com.android.server.pm.pkg.PackageUserState]\nprivate static final int HIDDEN\nprivate static final int INSTALLED\nprivate static final int INSTANT_APP\nprivate static final int NOT_LAUNCHED\nprivate static final int STOPPED\nprivate static final int SUSPENDED\nprivate static final int VIRTUAL_PRELOAD\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false)")
@@ -609,6 +613,11 @@ public class PackageStateImpl implements PackageState {
}
@DataClass.Generated.Member
+ public @ApplicationInfo.HiddenApiEnforcementPolicy int getHiddenApiEnforcementPolicy() {
+ return mHiddenApiEnforcementPolicy;
+ }
+
+ @DataClass.Generated.Member
public long getLastModifiedTime() {
return mLastModifiedTime;
}
@@ -705,10 +714,10 @@ public class PackageStateImpl implements PackageState {
}
@DataClass.Generated(
- time = 1665778832668L,
+ time = 1666719622749L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java",
- inputSignatures = "private int mBooleans\nprivate final @android.annotation.Nullable com.android.server.pm.pkg.AndroidPackage mAndroidPackage\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.Nullable java.lang.String mVolumeUuid\nprivate final int mAppId\nprivate final int mCategoryOverride\nprivate final @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate final long mLastModifiedTime\nprivate final long mLastUpdateTime\nprivate final long mLongVersionCode\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mMimeGroups\nprivate final @android.annotation.NonNull java.io.File mPath\nprivate final @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate final @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate final @android.annotation.Nullable java.lang.String mSeInfo\nprivate final boolean mHasSharedUser\nprivate final int mSharedUserAppId\nprivate final @android.annotation.NonNull java.lang.String[] mUsesSdkLibraries\nprivate final @android.annotation.NonNull long[] mUsesSdkLibrariesVersionsMajor\nprivate final @android.annotation.NonNull java.lang.String[] mUsesStaticLibraries\nprivate final @android.annotation.NonNull long[] mUsesStaticLibrariesVersions\nprivate final @android.annotation.NonNull java.util.List<com.android.server.pm.pkg.SharedLibrary> mUsesLibraries\nprivate final @android.annotation.NonNull java.util.List<java.lang.String> mUsesLibraryFiles\nprivate final @android.annotation.NonNull long[] mLastPackageUsageTime\nprivate final @android.annotation.NonNull android.content.pm.SigningInfo mSigningInfo\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserState> mUserStates\npublic static com.android.server.pm.pkg.PackageState copy(com.android.server.pm.pkg.PackageStateInternal)\nprivate void setBoolean(int,boolean)\nprivate boolean getBoolean(int)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserState getStateForUser(android.os.UserHandle)\npublic @java.lang.Override boolean isExternalStorage()\npublic @java.lang.Override boolean isForceQueryableOverride()\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @java.lang.Override boolean isInstallPermissionsFixed()\npublic @java.lang.Override boolean isOdm()\npublic @java.lang.Override boolean isOem()\npublic @java.lang.Override boolean isPrivileged()\npublic @java.lang.Override boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic @java.lang.Override boolean isSystem()\npublic @java.lang.Override boolean isSystemExt()\npublic @java.lang.Override boolean isUpdateAvailable()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic @java.lang.Override boolean isVendor()\npublic @java.lang.Override long getVersionCode()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override int getSharedUserAppId()\nclass PackageStateImpl extends java.lang.Object implements [com.android.server.pm.pkg.PackageState]\nprivate static final int SYSTEM\nprivate static final int EXTERNAL_STORAGE\nprivate static final int PRIVILEGED\nprivate static final int OEM\nprivate static final int VENDOR\nprivate static final int PRODUCT\nprivate static final int SYSTEM_EXT\nprivate static final int REQUIRED_FOR_SYSTEM_USER\nprivate static final int ODM\nprivate static final int FORCE_QUERYABLE_OVERRIDE\nprivate static final int HIDDEN_UNTIL_INSTALLED\nprivate static final int INSTALL_PERMISSIONS_FIXED\nprivate static final int UPDATE_AVAILABLE\nprivate static final int UPDATED_SYSTEM_APP\nprivate static final int APK_IN_UPDATED_APEX\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false)")
+ inputSignatures = "private int mBooleans\nprivate final @android.annotation.Nullable com.android.server.pm.pkg.AndroidPackage mAndroidPackage\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.Nullable java.lang.String mVolumeUuid\nprivate final int mAppId\nprivate final int mCategoryOverride\nprivate final @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate final @android.content.pm.ApplicationInfo.HiddenApiEnforcementPolicy int mHiddenApiEnforcementPolicy\nprivate final long mLastModifiedTime\nprivate final long mLastUpdateTime\nprivate final long mLongVersionCode\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mMimeGroups\nprivate final @android.annotation.NonNull java.io.File mPath\nprivate final @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate final @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate final @android.annotation.Nullable java.lang.String mSeInfo\nprivate final boolean mHasSharedUser\nprivate final int mSharedUserAppId\nprivate final @android.annotation.NonNull java.lang.String[] mUsesSdkLibraries\nprivate final @android.annotation.NonNull long[] mUsesSdkLibrariesVersionsMajor\nprivate final @android.annotation.NonNull java.lang.String[] mUsesStaticLibraries\nprivate final @android.annotation.NonNull long[] mUsesStaticLibrariesVersions\nprivate final @android.annotation.NonNull java.util.List<com.android.server.pm.pkg.SharedLibrary> mUsesLibraries\nprivate final @android.annotation.NonNull java.util.List<java.lang.String> mUsesLibraryFiles\nprivate final @android.annotation.NonNull long[] mLastPackageUsageTime\nprivate final @android.annotation.NonNull android.content.pm.SigningInfo mSigningInfo\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserState> mUserStates\npublic static com.android.server.pm.pkg.PackageState copy(com.android.server.pm.pkg.PackageStateInternal)\nprivate void setBoolean(int,boolean)\nprivate boolean getBoolean(int)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserState getStateForUser(android.os.UserHandle)\npublic @java.lang.Override boolean isExternalStorage()\npublic @java.lang.Override boolean isForceQueryableOverride()\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @java.lang.Override boolean isInstallPermissionsFixed()\npublic @java.lang.Override boolean isOdm()\npublic @java.lang.Override boolean isOem()\npublic @java.lang.Override boolean isPrivileged()\npublic @java.lang.Override boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic @java.lang.Override boolean isSystem()\npublic @java.lang.Override boolean isSystemExt()\npublic @java.lang.Override boolean isUpdateAvailable()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic @java.lang.Override boolean isVendor()\npublic @java.lang.Override long getVersionCode()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override int getSharedUserAppId()\nclass PackageStateImpl extends java.lang.Object implements [com.android.server.pm.pkg.PackageState]\nprivate static final int SYSTEM\nprivate static final int EXTERNAL_STORAGE\nprivate static final int PRIVILEGED\nprivate static final int OEM\nprivate static final int VENDOR\nprivate static final int PRODUCT\nprivate static final int SYSTEM_EXT\nprivate static final int REQUIRED_FOR_SYSTEM_USER\nprivate static final int ODM\nprivate static final int FORCE_QUERYABLE_OVERRIDE\nprivate static final int HIDDEN_UNTIL_INSTALLED\nprivate static final int INSTALL_PERMISSIONS_FIXED\nprivate static final int UPDATE_AVAILABLE\nprivate static final int UPDATED_SYSTEM_APP\nprivate static final int APK_IN_UPDATED_APEX\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false)")
@Deprecated
private void __metadata() {}
diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java b/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java
index a536f90fea6b..b3deb1cca8d2 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java
@@ -44,7 +44,7 @@ import java.util.Objects;
/** @hide */
@DataClass(genConstructor = false, genBuilder = false, genEqualsHashCode = true)
@DataClass.Suppress({"mOverlayPathsLock", "mOverlayPaths", "mSharedLibraryOverlayPathsLock",
- "mSharedLibraryOverlayPaths", "setOverlayPaths", "setCachedOverlayPaths"})
+ "mSharedLibraryOverlayPaths", "setOverlayPaths", "setCachedOverlayPaths", "getWatchable"})
public class PackageUserStateImpl extends WatchableImpl implements PackageUserStateInternal,
Snappable {
@@ -92,8 +92,9 @@ public class PackageUserStateImpl extends WatchableImpl implements PackageUserSt
private long mFirstInstallTime;
+ // TODO(b/239050028): Remove, enforce notifying parent through PMS commit method
@Nullable
- private final Watchable mWatchable;
+ private Watchable mWatchable;
@NonNull
final SnapshotCache<PackageUserStateImpl> mSnapshot;
@@ -550,71 +551,30 @@ public class PackageUserStateImpl extends WatchableImpl implements PackageUserSt
? Collections.emptyMap() : mSharedLibraryOverlayPaths;
}
- @Override
- public boolean equals(@Nullable Object o) {
- // You can override field equality logic by defining either of the methods like:
- // boolean fieldNameEquals(PackageUserStateImpl other) { ... }
- // boolean fieldNameEquals(FieldType otherValue) { ... }
+ @NonNull
+ public PackageUserStateImpl setWatchable(@NonNull Watchable watchable) {
+ mWatchable = watchable;
+ return this;
+ }
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
- @SuppressWarnings("unchecked")
- PackageUserStateImpl that = (PackageUserStateImpl) o;
- //noinspection PointlessBooleanExpression
- return Objects.equals(mDisabledComponentsWatched, that.mDisabledComponentsWatched)
- && Objects.equals(mEnabledComponentsWatched, that.mEnabledComponentsWatched)
- && mCeDataInode == that.mCeDataInode
- && mInstalled == that.mInstalled
- && mStopped == that.mStopped
- && mNotLaunched == that.mNotLaunched
- && mHidden == that.mHidden
- && mDistractionFlags == that.mDistractionFlags
- && mInstantApp == that.mInstantApp
- && mVirtualPreload == that.mVirtualPreload
- && mEnabledState == that.mEnabledState
- && mInstallReason == that.mInstallReason
- && mUninstallReason == that.mUninstallReason
- && Objects.equals(mHarmfulAppWarning, that.mHarmfulAppWarning)
- && Objects.equals(mLastDisableAppCaller, that.mLastDisableAppCaller)
- && Objects.equals(mOverlayPaths, that.mOverlayPaths)
- && Objects.equals(mSharedLibraryOverlayPaths, that.mSharedLibraryOverlayPaths)
- && Objects.equals(mSplashScreenTheme, that.mSplashScreenTheme)
- && Objects.equals(mSuspendParams, that.mSuspendParams)
- && Objects.equals(mComponentLabelIconOverrideMap,
- that.mComponentLabelIconOverrideMap)
- && mFirstInstallTime == that.mFirstInstallTime
- && Objects.equals(mWatchable, that.mWatchable);
+ private boolean watchableEquals(Watchable other) {
+ // Ignore the Watchable for equality
+ return true;
}
- @Override
- public int hashCode() {
- // You can override field hashCode logic by defining methods like:
- // int fieldNameHashCode() { ... }
+ private int watchableHashCode() {
+ // Ignore the Watchable for equality
+ return 0;
+ }
- int _hash = 1;
- _hash = 31 * _hash + Objects.hashCode(mDisabledComponentsWatched);
- _hash = 31 * _hash + Objects.hashCode(mEnabledComponentsWatched);
- _hash = 31 * _hash + Long.hashCode(mCeDataInode);
- _hash = 31 * _hash + Boolean.hashCode(mInstalled);
- _hash = 31 * _hash + Boolean.hashCode(mStopped);
- _hash = 31 * _hash + Boolean.hashCode(mNotLaunched);
- _hash = 31 * _hash + Boolean.hashCode(mHidden);
- _hash = 31 * _hash + mDistractionFlags;
- _hash = 31 * _hash + Boolean.hashCode(mInstantApp);
- _hash = 31 * _hash + Boolean.hashCode(mVirtualPreload);
- _hash = 31 * _hash + mEnabledState;
- _hash = 31 * _hash + mInstallReason;
- _hash = 31 * _hash + mUninstallReason;
- _hash = 31 * _hash + Objects.hashCode(mHarmfulAppWarning);
- _hash = 31 * _hash + Objects.hashCode(mLastDisableAppCaller);
- _hash = 31 * _hash + Objects.hashCode(mOverlayPaths);
- _hash = 31 * _hash + Objects.hashCode(mSharedLibraryOverlayPaths);
- _hash = 31 * _hash + Objects.hashCode(mSplashScreenTheme);
- _hash = 31 * _hash + Objects.hashCode(mSuspendParams);
- _hash = 31 * _hash + Objects.hashCode(mComponentLabelIconOverrideMap);
- _hash = 31 * _hash + Long.hashCode(mFirstInstallTime);
- _hash = 31 * _hash + Objects.hashCode(mWatchable);
- return _hash;
+ private boolean snapshotEquals(SnapshotCache<PackageUserStateImpl> other) {
+ // Ignore the SnapshotCache for equality
+ return true;
+ }
+
+ private int snapshotHashCode() {
+ // Ignore the SnapshotCache for equality
+ return 0;
}
@@ -736,11 +696,6 @@ public class PackageUserStateImpl extends WatchableImpl implements PackageUserSt
}
@DataClass.Generated.Member
- public @Nullable Watchable getWatchable() {
- return mWatchable;
- }
-
- @DataClass.Generated.Member
public @NonNull SnapshotCache<PackageUserStateImpl> getSnapshot() {
return mSnapshot;
}
@@ -778,11 +733,82 @@ public class PackageUserStateImpl extends WatchableImpl implements PackageUserSt
return this;
}
+ @Override
+ @DataClass.Generated.Member
+ public boolean equals(@Nullable Object o) {
+ // You can override field equality logic by defining either of the methods like:
+ // boolean fieldNameEquals(PackageUserStateImpl other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ PackageUserStateImpl that = (PackageUserStateImpl) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && Objects.equals(mDisabledComponentsWatched, that.mDisabledComponentsWatched)
+ && Objects.equals(mEnabledComponentsWatched, that.mEnabledComponentsWatched)
+ && mCeDataInode == that.mCeDataInode
+ && mInstalled == that.mInstalled
+ && mStopped == that.mStopped
+ && mNotLaunched == that.mNotLaunched
+ && mHidden == that.mHidden
+ && mDistractionFlags == that.mDistractionFlags
+ && mInstantApp == that.mInstantApp
+ && mVirtualPreload == that.mVirtualPreload
+ && mEnabledState == that.mEnabledState
+ && mInstallReason == that.mInstallReason
+ && mUninstallReason == that.mUninstallReason
+ && Objects.equals(mHarmfulAppWarning, that.mHarmfulAppWarning)
+ && Objects.equals(mLastDisableAppCaller, that.mLastDisableAppCaller)
+ && Objects.equals(mOverlayPaths, that.mOverlayPaths)
+ && Objects.equals(mSharedLibraryOverlayPaths, that.mSharedLibraryOverlayPaths)
+ && Objects.equals(mSplashScreenTheme, that.mSplashScreenTheme)
+ && Objects.equals(mSuspendParams, that.mSuspendParams)
+ && Objects.equals(mComponentLabelIconOverrideMap, that.mComponentLabelIconOverrideMap)
+ && mFirstInstallTime == that.mFirstInstallTime
+ && watchableEquals(that.mWatchable)
+ && snapshotEquals(that.mSnapshot);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int hashCode() {
+ // You can override field hashCode logic by defining methods like:
+ // int fieldNameHashCode() { ... }
+
+ int _hash = 1;
+ _hash = 31 * _hash + Objects.hashCode(mDisabledComponentsWatched);
+ _hash = 31 * _hash + Objects.hashCode(mEnabledComponentsWatched);
+ _hash = 31 * _hash + Long.hashCode(mCeDataInode);
+ _hash = 31 * _hash + Boolean.hashCode(mInstalled);
+ _hash = 31 * _hash + Boolean.hashCode(mStopped);
+ _hash = 31 * _hash + Boolean.hashCode(mNotLaunched);
+ _hash = 31 * _hash + Boolean.hashCode(mHidden);
+ _hash = 31 * _hash + mDistractionFlags;
+ _hash = 31 * _hash + Boolean.hashCode(mInstantApp);
+ _hash = 31 * _hash + Boolean.hashCode(mVirtualPreload);
+ _hash = 31 * _hash + mEnabledState;
+ _hash = 31 * _hash + mInstallReason;
+ _hash = 31 * _hash + mUninstallReason;
+ _hash = 31 * _hash + Objects.hashCode(mHarmfulAppWarning);
+ _hash = 31 * _hash + Objects.hashCode(mLastDisableAppCaller);
+ _hash = 31 * _hash + Objects.hashCode(mOverlayPaths);
+ _hash = 31 * _hash + Objects.hashCode(mSharedLibraryOverlayPaths);
+ _hash = 31 * _hash + Objects.hashCode(mSplashScreenTheme);
+ _hash = 31 * _hash + Objects.hashCode(mSuspendParams);
+ _hash = 31 * _hash + Objects.hashCode(mComponentLabelIconOverrideMap);
+ _hash = 31 * _hash + Long.hashCode(mFirstInstallTime);
+ _hash = 31 * _hash + watchableHashCode();
+ _hash = 31 * _hash + snapshotHashCode();
+ return _hash;
+ }
+
@DataClass.Generated(
- time = 1645040852569L,
+ time = 1668033772891L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java",
- inputSignatures = "protected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mDisabledComponentsWatched\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mEnabledComponentsWatched\nprivate long mCeDataInode\nprivate boolean mInstalled\nprivate boolean mStopped\nprivate boolean mNotLaunched\nprivate boolean mHidden\nprivate int mDistractionFlags\nprivate boolean mInstantApp\nprivate boolean mVirtualPreload\nprivate int mEnabledState\nprivate @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate @android.annotation.Nullable android.content.pm.overlay.OverlayPaths mOverlayPaths\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams> mSuspendParams\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>> mComponentLabelIconOverrideMap\nprivate long mFirstInstallTime\nprivate final @android.annotation.Nullable com.android.server.utils.Watchable mWatchable\nfinal @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> mSnapshot\nprivate com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> makeCache()\nprivate void onChanged()\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserStateImpl snapshot()\npublic @android.annotation.Nullable boolean setOverlayPaths(android.content.pm.overlay.OverlayPaths)\npublic boolean setSharedLibraryOverlayPaths(java.lang.String,android.content.pm.overlay.OverlayPaths)\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponentsNoCopy()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponentsNoCopy()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getDisabledComponents()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getEnabledComponents()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer)\npublic void resetOverrideComponentLabelIcon()\npublic @android.annotation.Nullable android.util.Pair<java.lang.String,java.lang.Integer> getOverrideLabelIconForComponent(android.content.ComponentName)\npublic @java.lang.Override boolean isSuspended()\npublic com.android.server.pm.pkg.PackageUserStateImpl putSuspendParams(java.lang.String,com.android.server.pm.pkg.SuspendParams)\npublic com.android.server.pm.pkg.PackageUserStateImpl removeSuspension(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setCeDataInode(long)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstalled(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setStopped(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setNotLaunched(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHidden(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDistractionFlags(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstantApp(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setVirtualPreload(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setUninstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHarmfulAppWarning(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setLastDisableAppCaller(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSharedLibraryOverlayPaths(android.util.ArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSplashScreenTheme(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSuspendParams(android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setComponentLabelIconOverrideMap(android.util.ArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setFirstInstallTime(long)\npublic @android.annotation.NonNull @java.lang.Override java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> getSharedLibraryOverlayPaths()\npublic @java.lang.Override boolean equals(java.lang.Object)\npublic @java.lang.Override int hashCode()\nclass PackageUserStateImpl extends com.android.server.utils.WatchableImpl implements [com.android.server.pm.pkg.PackageUserStateInternal, com.android.server.utils.Snappable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=false, genEqualsHashCode=true)")
+ inputSignatures = "protected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mDisabledComponentsWatched\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mEnabledComponentsWatched\nprivate long mCeDataInode\nprivate boolean mInstalled\nprivate boolean mStopped\nprivate boolean mNotLaunched\nprivate boolean mHidden\nprivate int mDistractionFlags\nprivate boolean mInstantApp\nprivate boolean mVirtualPreload\nprivate int mEnabledState\nprivate @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate @android.annotation.Nullable android.content.pm.overlay.OverlayPaths mOverlayPaths\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams> mSuspendParams\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>> mComponentLabelIconOverrideMap\nprivate long mFirstInstallTime\nprivate @android.annotation.Nullable com.android.server.utils.Watchable mWatchable\nfinal @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> mSnapshot\nprivate com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> makeCache()\nprivate void onChanged()\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserStateImpl snapshot()\npublic @android.annotation.Nullable boolean setOverlayPaths(android.content.pm.overlay.OverlayPaths)\npublic boolean setSharedLibraryOverlayPaths(java.lang.String,android.content.pm.overlay.OverlayPaths)\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponentsNoCopy()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponentsNoCopy()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getDisabledComponents()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getEnabledComponents()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer)\npublic void resetOverrideComponentLabelIcon()\npublic @android.annotation.Nullable android.util.Pair<java.lang.String,java.lang.Integer> getOverrideLabelIconForComponent(android.content.ComponentName)\npublic @java.lang.Override boolean isSuspended()\npublic com.android.server.pm.pkg.PackageUserStateImpl putSuspendParams(java.lang.String,com.android.server.pm.pkg.SuspendParams)\npublic com.android.server.pm.pkg.PackageUserStateImpl removeSuspension(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setCeDataInode(long)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstalled(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setStopped(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setNotLaunched(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHidden(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDistractionFlags(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstantApp(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setVirtualPreload(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setUninstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHarmfulAppWarning(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setLastDisableAppCaller(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSharedLibraryOverlayPaths(android.util.ArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSplashScreenTheme(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSuspendParams(android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setComponentLabelIconOverrideMap(android.util.ArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setFirstInstallTime(long)\npublic @android.annotation.NonNull @java.lang.Override java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> getSharedLibraryOverlayPaths()\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setWatchable(com.android.server.utils.Watchable)\nprivate boolean watchableEquals(com.android.server.utils.Watchable)\nprivate int watchableHashCode()\nprivate boolean snapshotEquals(com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl>)\nprivate int snapshotHashCode()\nclass PackageUserStateImpl extends com.android.server.utils.WatchableImpl implements [com.android.server.pm.pkg.PackageUserStateInternal, com.android.server.utils.Snappable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=false, genEqualsHashCode=true)")
@Deprecated
private void __metadata() {}
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedActivity.java b/services/core/java/com/android/server/pm/pkg/component/ParsedActivity.java
index e019215f1ad8..1826f7a38e26 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedActivity.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedActivity.java
@@ -98,8 +98,8 @@ public interface ParsedActivity extends ParsedMainComponent {
boolean isSupportsSizeChanges();
/**
- * Gets the category of the target display this activity is supposed to run on.
+ * Gets the required category of the display this activity is supposed to run on.
*/
@Nullable
- String getTargetDisplayCategory();
+ String getRequiredDisplayCategory();
}
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedActivityImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedActivityImpl.java
index 278e547dd742..68d5428d6604 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedActivityImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedActivityImpl.java
@@ -97,7 +97,7 @@ public class ParsedActivityImpl extends ParsedMainComponentImpl implements Parse
private ActivityInfo.WindowLayout windowLayout;
@Nullable
- private String mTargetDisplayCategory;
+ private String mRequiredDisplayCategory;
public ParsedActivityImpl(ParsedActivityImpl other) {
super(other);
@@ -125,7 +125,7 @@ public class ParsedActivityImpl extends ParsedMainComponentImpl implements Parse
this.colorMode = other.colorMode;
this.windowLayout = other.windowLayout;
this.mKnownActivityEmbeddingCerts = other.mKnownActivityEmbeddingCerts;
- this.mTargetDisplayCategory = other.mTargetDisplayCategory;
+ this.mRequiredDisplayCategory = other.mRequiredDisplayCategory;
}
/**
@@ -193,7 +193,7 @@ public class ParsedActivityImpl extends ParsedMainComponentImpl implements Parse
alias.requestedVrComponent = target.getRequestedVrComponent();
alias.setDirectBootAware(target.isDirectBootAware());
alias.setProcessName(target.getProcessName());
- alias.setTargetDisplayCategory(target.getTargetDisplayCategory());
+ alias.setRequiredDisplayCategory(target.getRequiredDisplayCategory());
return alias;
// Not all attributes from the target ParsedActivity are copied to the alias.
@@ -321,7 +321,7 @@ public class ParsedActivityImpl extends ParsedMainComponentImpl implements Parse
dest.writeBoolean(false);
}
sForStringSet.parcel(this.mKnownActivityEmbeddingCerts, dest, flags);
- dest.writeString8(this.mTargetDisplayCategory);
+ dest.writeString8(this.mRequiredDisplayCategory);
}
public ParsedActivityImpl() {
@@ -356,7 +356,7 @@ public class ParsedActivityImpl extends ParsedMainComponentImpl implements Parse
windowLayout = new ActivityInfo.WindowLayout(in);
}
this.mKnownActivityEmbeddingCerts = sForStringSet.unparcel(in);
- this.mTargetDisplayCategory = in.readString8();
+ this.mRequiredDisplayCategory = in.readString8();
}
@NonNull
@@ -414,7 +414,7 @@ public class ParsedActivityImpl extends ParsedMainComponentImpl implements Parse
int rotationAnimation,
int colorMode,
@Nullable ActivityInfo.WindowLayout windowLayout,
- @Nullable String targetDisplayCategory) {
+ @Nullable String requiredDisplayCategory) {
this.theme = theme;
this.uiOptions = uiOptions;
this.targetActivity = targetActivity;
@@ -439,7 +439,7 @@ public class ParsedActivityImpl extends ParsedMainComponentImpl implements Parse
this.rotationAnimation = rotationAnimation;
this.colorMode = colorMode;
this.windowLayout = windowLayout;
- this.mTargetDisplayCategory = targetDisplayCategory;
+ this.mRequiredDisplayCategory = requiredDisplayCategory;
// onConstructed(); // You can define this method to get a callback
}
@@ -560,8 +560,8 @@ public class ParsedActivityImpl extends ParsedMainComponentImpl implements Parse
}
@DataClass.Generated.Member
- public @Nullable String getTargetDisplayCategory() {
- return mTargetDisplayCategory;
+ public @Nullable String getRequiredDisplayCategory() {
+ return mRequiredDisplayCategory;
}
@DataClass.Generated.Member
@@ -691,16 +691,16 @@ public class ParsedActivityImpl extends ParsedMainComponentImpl implements Parse
}
@DataClass.Generated.Member
- public @NonNull ParsedActivityImpl setTargetDisplayCategory(@NonNull String value) {
- mTargetDisplayCategory = value;
+ public @NonNull ParsedActivityImpl setRequiredDisplayCategory(@NonNull String value) {
+ mRequiredDisplayCategory = value;
return this;
}
@DataClass.Generated(
- time = 1664805688714L,
+ time = 1669437519576L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/component/ParsedActivityImpl.java",
- inputSignatures = "private int theme\nprivate int uiOptions\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String targetActivity\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String parentActivityName\nprivate @android.annotation.Nullable java.lang.String taskAffinity\nprivate int privateFlags\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String permission\nprivate @android.annotation.Nullable java.util.Set<java.lang.String> mKnownActivityEmbeddingCerts\nprivate int launchMode\nprivate int documentLaunchMode\nprivate int maxRecents\nprivate int configChanges\nprivate int softInputMode\nprivate int persistableMode\nprivate int lockTaskLaunchMode\nprivate int screenOrientation\nprivate int resizeMode\nprivate float maxAspectRatio\nprivate float minAspectRatio\nprivate boolean supportsSizeChanges\nprivate @android.annotation.Nullable java.lang.String requestedVrComponent\nprivate int rotationAnimation\nprivate int colorMode\nprivate @android.annotation.Nullable android.content.pm.ActivityInfo.WindowLayout windowLayout\nprivate @android.annotation.Nullable java.lang.String mTargetDisplayCategory\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<com.android.server.pm.pkg.component.ParsedActivityImpl> CREATOR\nstatic @android.annotation.NonNull com.android.server.pm.pkg.component.ParsedActivityImpl makeAppDetailsActivity(java.lang.String,java.lang.String,int,java.lang.String,boolean)\nstatic @android.annotation.NonNull com.android.server.pm.pkg.component.ParsedActivityImpl makeAlias(java.lang.String,com.android.server.pm.pkg.component.ParsedActivity)\npublic com.android.server.pm.pkg.component.ParsedActivityImpl setMaxAspectRatio(int,float)\npublic com.android.server.pm.pkg.component.ParsedActivityImpl setMinAspectRatio(int,float)\npublic com.android.server.pm.pkg.component.ParsedActivityImpl setTargetActivity(java.lang.String)\npublic com.android.server.pm.pkg.component.ParsedActivityImpl setPermission(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override java.util.Set<java.lang.String> getKnownActivityEmbeddingCerts()\npublic void setKnownActivityEmbeddingCerts(java.util.Set<java.lang.String>)\npublic java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedActivityImpl extends com.android.server.pm.pkg.component.ParsedMainComponentImpl implements [com.android.server.pm.pkg.component.ParsedActivity, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=false)")
+ inputSignatures = "private int theme\nprivate int uiOptions\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String targetActivity\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String parentActivityName\nprivate @android.annotation.Nullable java.lang.String taskAffinity\nprivate int privateFlags\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String permission\nprivate @android.annotation.Nullable java.util.Set<java.lang.String> mKnownActivityEmbeddingCerts\nprivate int launchMode\nprivate int documentLaunchMode\nprivate int maxRecents\nprivate int configChanges\nprivate int softInputMode\nprivate int persistableMode\nprivate int lockTaskLaunchMode\nprivate int screenOrientation\nprivate int resizeMode\nprivate float maxAspectRatio\nprivate float minAspectRatio\nprivate boolean supportsSizeChanges\nprivate @android.annotation.Nullable java.lang.String requestedVrComponent\nprivate int rotationAnimation\nprivate int colorMode\nprivate @android.annotation.Nullable android.content.pm.ActivityInfo.WindowLayout windowLayout\nprivate @android.annotation.Nullable java.lang.String mRequiredDisplayCategory\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<com.android.server.pm.pkg.component.ParsedActivityImpl> CREATOR\nstatic @android.annotation.NonNull com.android.server.pm.pkg.component.ParsedActivityImpl makeAppDetailsActivity(java.lang.String,java.lang.String,int,java.lang.String,boolean)\nstatic @android.annotation.NonNull com.android.server.pm.pkg.component.ParsedActivityImpl makeAlias(java.lang.String,com.android.server.pm.pkg.component.ParsedActivity)\npublic com.android.server.pm.pkg.component.ParsedActivityImpl setMaxAspectRatio(int,float)\npublic com.android.server.pm.pkg.component.ParsedActivityImpl setMinAspectRatio(int,float)\npublic com.android.server.pm.pkg.component.ParsedActivityImpl setTargetActivity(java.lang.String)\npublic com.android.server.pm.pkg.component.ParsedActivityImpl setPermission(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override java.util.Set<java.lang.String> getKnownActivityEmbeddingCerts()\npublic void setKnownActivityEmbeddingCerts(java.util.Set<java.lang.String>)\npublic java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedActivityImpl extends com.android.server.pm.pkg.component.ParsedMainComponentImpl implements [com.android.server.pm.pkg.component.ParsedActivity, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=false)")
@Deprecated
private void __metadata() {}
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java
index 305062b4ce7e..ea791e1f29db 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java
@@ -220,17 +220,17 @@ public class ParsedActivityUtils {
pkg.setVisibleToInstantApps(true);
}
- String targetDisplayCategory = sa.getNonConfigurationString(
- R.styleable.AndroidManifestActivity_targetDisplayCategory, 0);
+ String requiredDisplayCategory = sa.getNonConfigurationString(
+ R.styleable.AndroidManifestActivity_requiredDisplayCategory, 0);
- if (targetDisplayCategory != null
- && FrameworkParsingPackageUtils.validateName(targetDisplayCategory,
+ if (requiredDisplayCategory != null
+ && FrameworkParsingPackageUtils.validateName(requiredDisplayCategory,
false /* requireSeparator */, false /* requireFilename */) != null) {
- return input.error("targetDisplayCategory attribute can only consists of "
- + "alphanumeric characters, '_', and '.'");
+ return input.error("requiredDisplayCategory attribute can only consist "
+ + "of alphanumeric characters, '_', and '.'");
}
- activity.setTargetDisplayCategory(targetDisplayCategory);
+ activity.setRequiredDisplayCategory(requiredDisplayCategory);
return parseActivityOrAlias(activity, pkg, tag, parser, res, sa, receiver,
false /*isAlias*/, visibleToEphemeral, input,
diff --git a/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java b/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java
index e736f433c11c..4a8ef963959b 100644
--- a/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java
+++ b/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java
@@ -40,6 +40,8 @@ public class PackageStateMutator {
private final Function<String, PackageSetting> mActiveStateFunction;
private final Function<String, PackageSetting> mDisabledStateFunction;
+ private final ArraySet<PackageSetting> mChangedStates = new ArraySet<>();
+
public PackageStateMutator(@NonNull Function<String, PackageSetting> activeStateFunction,
@NonNull Function<String, PackageSetting> disabledStateFunction) {
mActiveStateFunction = activeStateFunction;
@@ -52,23 +54,23 @@ public class PackageStateMutator {
@NonNull
public PackageStateWrite forPackage(@NonNull String packageName) {
- return mStateWrite.setState(mActiveStateFunction.apply(packageName));
+ return setState(mActiveStateFunction.apply(packageName));
}
@Nullable
public PackageStateWrite forPackageNullable(@NonNull String packageName) {
final PackageSetting packageState = mActiveStateFunction.apply(packageName);
- mStateWrite.setState(packageState);
+ setState(packageState);
if (packageState == null) {
return null;
}
- return mStateWrite.setState(packageState);
+ return setState(packageState);
}
@NonNull
public PackageStateWrite forDisabledSystemPackage(@NonNull String packageName) {
- return mStateWrite.setState(mDisabledStateFunction.apply(packageName));
+ return setState(mDisabledStateFunction.apply(packageName));
}
@Nullable
@@ -78,7 +80,7 @@ public class PackageStateMutator {
return null;
}
- return mStateWrite.setState(packageState);
+ return setState(packageState);
}
@NonNull
@@ -109,6 +111,21 @@ public class PackageStateMutator {
}
}
+ public void onFinished() {
+ for (int index = 0; index < mChangedStates.size(); index++) {
+ mChangedStates.valueAt(index).onChanged();
+ }
+ }
+
+ @NonNull
+ private StateWriteWrapper setState(@Nullable PackageSetting state) {
+ // State can be nullable because this infrastructure no-ops on non-existent states
+ if (state != null) {
+ mChangedStates.add(state);
+ }
+ return mStateWrite.setState(state);
+ }
+
public static class InitialState {
private final int mPackageSequence;
@@ -173,8 +190,11 @@ public class PackageStateMutator {
@NonNull
@Override
public PackageUserStateWrite userState(int userId) {
- return mUserStateWrite.setStates(
- mState == null ? null : mState.getOrCreateUserState(userId));
+ var userState = mState == null ? null : mState.getOrCreateUserState(userId);
+ if (userState != null) {
+ userState.setWatchable(mState);
+ }
+ return mUserStateWrite.setStates(userState);
}
@Override
diff --git a/services/core/java/com/android/server/pm/snapshot/PackageDataSnapshot.java b/services/core/java/com/android/server/pm/snapshot/PackageDataSnapshot.java
index b2080b2d43b1..90a0c7cdf609 100644
--- a/services/core/java/com/android/server/pm/snapshot/PackageDataSnapshot.java
+++ b/services/core/java/com/android/server/pm/snapshot/PackageDataSnapshot.java
@@ -16,7 +16,6 @@
package com.android.server.pm.snapshot;
-import android.annotation.SystemApi;
import android.content.pm.PackageManagerInternal;
import com.android.server.pm.Computer;
@@ -32,6 +31,5 @@ import com.android.server.pm.PackageManagerService;
*
* @hide
*/
-@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
public interface PackageDataSnapshot {
}
diff --git a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
index d2e0502ad70c..91bb677524cf 100644
--- a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
+++ b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
@@ -378,13 +378,14 @@ public final class DeviceStateProviderImpl implements DeviceStateProvider,
try {
conditionSatisfied = mStateConditions.get(state).getAsBoolean();
} catch (IllegalStateException e) {
- // Failed to compute the current state based on current available data. Return
+ // Failed to compute the current state based on current available data. Continue
// with the expectation that notifyDeviceStateChangedIfNeeded() will be called
- // when a callback with the missing data is triggered.
+ // when a callback with the missing data is triggered. May trigger another state
+ // change if another state is satisfied currently.
if (DEBUG) {
Slog.d(TAG, "Unable to check current state", e);
}
- return;
+ continue;
}
if (conditionSatisfied) {
diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java
index e61effa13a60..d6cac3352321 100644
--- a/services/core/java/com/android/server/policy/PermissionPolicyService.java
+++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java
@@ -232,7 +232,7 @@ public final class PermissionPolicyService extends SystemService {
}
};
- final ArrayList<PermissionInfo> dangerousPerms =
+ final List<PermissionInfo> dangerousPerms =
mPermissionManagerInternal.getAllPermissionsWithProtection(
PermissionInfo.PROTECTION_DANGEROUS);
try {
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index e9c93eef6c54..a4a085358c56 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -151,6 +151,7 @@ import android.os.Vibrator;
import android.provider.DeviceConfig;
import android.provider.MediaStore;
import android.provider.Settings;
+import android.provider.Settings.Secure;
import android.service.dreams.DreamManagerInternal;
import android.service.dreams.DreamService;
import android.service.dreams.IDreamManager;
@@ -516,6 +517,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
int mDoublePressOnStemPrimaryBehavior;
int mTriplePressOnStemPrimaryBehavior;
int mLongPressOnStemPrimaryBehavior;
+ boolean mStylusButtonsDisabled = false;
boolean mHasSoftInput = false;
boolean mHapticTextHandleEnabled;
boolean mUseTvRouting;
@@ -771,6 +773,9 @@ public class PhoneWindowManager implements WindowManagerPolicy {
resolver.registerContentObserver(Settings.Global.getUriFor(
Settings.Global.POWER_BUTTON_SUPPRESSION_DELAY_AFTER_GESTURE_WAKE), false, this,
UserHandle.USER_ALL);
+ resolver.registerContentObserver(Settings.Secure.getUriFor(
+ Settings.Secure.STYLUS_BUTTONS_DISABLED), false, this,
+ UserHandle.USER_ALL);
updateSettings();
}
@@ -1709,6 +1714,11 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
}
+ private void showSystemSettings() {
+ startActivityAsUser(new Intent(android.provider.Settings.ACTION_SETTINGS),
+ UserHandle.CURRENT_OR_SELF);
+ }
+
private void showPictureInPictureMenu(KeyEvent event) {
if (DEBUG_INPUT) Log.d(TAG, "showPictureInPictureMenu event=" + event);
mHandler.removeMessages(MSG_SHOW_PICTURE_IN_PICTURE_MENU);
@@ -2560,6 +2570,9 @@ public class PhoneWindowManager implements WindowManagerPolicy {
Settings.Global.KEY_CHORD_POWER_VOLUME_UP,
mContext.getResources().getInteger(
com.android.internal.R.integer.config_keyChordPowerVolumeUp));
+
+ mStylusButtonsDisabled = Settings.Secure.getIntForUser(resolver,
+ Secure.STYLUS_BUTTONS_DISABLED, 0, UserHandle.USER_CURRENT) == 1;
}
if (updateRotation) {
updateRotation(true);
@@ -2892,6 +2905,20 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
}
return key_consumed;
+ case KeyEvent.KEYCODE_A:
+ if (down && event.isMetaPressed()) {
+ launchAssistAction(Intent.EXTRA_ASSIST_INPUT_HINT_KEYBOARD,
+ event.getDeviceId(),
+ event.getEventTime(), AssistUtils.INVOCATION_TYPE_UNKNOWN);
+ return key_consumed;
+ }
+ break;
+ case KeyEvent.KEYCODE_I:
+ if (down && event.isMetaPressed()) {
+ showSystemSettings();
+ return key_consumed;
+ }
+ break;
case KeyEvent.KEYCODE_N:
if (down && event.isMetaPressed()) {
IStatusBarService service = getStatusBarService();
@@ -2912,6 +2939,27 @@ public class PhoneWindowManager implements WindowManagerPolicy {
return key_consumed;
}
break;
+ case KeyEvent.KEYCODE_DPAD_UP:
+ if (down && event.isMetaPressed() && event.isCtrlPressed() && repeatCount == 0) {
+ StatusBarManagerInternal statusbar = getStatusBarManagerInternal();
+ if (statusbar != null) {
+ statusbar.goToFullscreenFromSplit();
+ }
+ return key_consumed;
+ }
+ break;
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ if (down && event.isMetaPressed() && event.isCtrlPressed() && repeatCount == 0) {
+ enterStageSplitFromRunningApp(true /* leftOrTop */);
+ return key_consumed;
+ }
+ break;
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
+ if (down && event.isMetaPressed() && event.isCtrlPressed() && repeatCount == 0) {
+ enterStageSplitFromRunningApp(false /* leftOrTop */);
+ return key_consumed;
+ }
+ break;
case KeyEvent.KEYCODE_SLASH:
if (down && repeatCount == 0 && event.isMetaPressed() && !keyguardOn) {
toggleKeyboardShortcutsMenu(event.getDeviceId());
@@ -3011,12 +3059,13 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
break;
case KeyEvent.KEYCODE_TAB:
- if (event.isMetaPressed()) {
- // Pass through keyboard navigation keys.
- return key_not_consumed;
- }
- // Display task switcher for ALT-TAB.
- if (down && repeatCount == 0) {
+ if (down && event.isMetaPressed()) {
+ if (!keyguardOn && isUserSetupComplete()) {
+ showRecentApps(false);
+ return key_consumed;
+ }
+ } else if (down && repeatCount == 0) {
+ // Display task switcher for ALT-TAB.
if (mRecentAppsHeldModifiers == 0 && !keyguardOn && isUserSetupComplete()) {
final int shiftlessModifiers =
event.getModifiers() & ~KeyEvent.META_SHIFT_MASK;
@@ -3071,9 +3120,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
mPendingCapsLockToggle = false;
} else if (mPendingMetaAction) {
if (!canceled) {
- launchAssistAction(Intent.EXTRA_ASSIST_INPUT_HINT_KEYBOARD,
- event.getDeviceId(),
- event.getEventTime(), AssistUtils.INVOCATION_TYPE_UNKNOWN);
+ // TODO: launch all apps here.
}
mPendingMetaAction = false;
}
@@ -3566,6 +3613,13 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
}
+ private void enterStageSplitFromRunningApp(boolean leftOrTop) {
+ StatusBarManagerInternal statusbar = getStatusBarManagerInternal();
+ if (statusbar != null) {
+ statusbar.enterStageSplitFromRunningApp(leftOrTop);
+ }
+ }
+
void launchHomeFromHotKey(int displayId) {
launchHomeFromHotKey(displayId, true /* awakenFromDreams */, true /*respectKeyguard*/);
}
@@ -4183,7 +4237,9 @@ public class PhoneWindowManager implements WindowManagerPolicy {
case KeyEvent.KEYCODE_DEMO_APP_3:
case KeyEvent.KEYCODE_DEMO_APP_4: {
// TODO(b/254604589): Dispatch KeyEvent to System UI.
- sendSystemKeyToStatusBarAsync(keyCode);
+ if (!mStylusButtonsDisabled) {
+ sendSystemKeyToStatusBarAsync(keyCode);
+ }
// Just drop if keys are not intercepted for direct key.
result &= ~ACTION_PASS_TO_USER;
diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
index 7737421654ee..85f13572db68 100644
--- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
+++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
@@ -200,6 +200,9 @@ public class KeyguardServiceDelegate {
if (!mKeyguardState.enabled) {
mKeyguardService.setKeyguardEnabled(mKeyguardState.enabled);
}
+ if (mKeyguardState.dreaming) {
+ mKeyguardService.onDreamingStarted();
+ }
}
@Override
diff --git a/services/core/java/com/android/server/power/PowerGroup.java b/services/core/java/com/android/server/power/PowerGroup.java
index 431cf3861804..1c4e143b27e6 100644
--- a/services/core/java/com/android/server/power/PowerGroup.java
+++ b/services/core/java/com/android/server/power/PowerGroup.java
@@ -324,11 +324,6 @@ public class PowerGroup {
return mDisplayPowerRequest.policy == DisplayPowerRequest.POLICY_DIM;
}
- public boolean isPolicyVrLocked() {
- return mDisplayPowerRequest.isVr();
-
- }
-
public boolean isBrightOrDimLocked() {
return mDisplayPowerRequest.isBrightOrDim();
}
@@ -382,7 +377,7 @@ public class PowerGroup {
@VisibleForTesting
int getDesiredScreenPolicyLocked(boolean quiescent, boolean dozeAfterScreenOff,
- boolean vrModeEnabled, boolean bootCompleted, boolean screenBrightnessBoostInProgress) {
+ boolean bootCompleted, boolean screenBrightnessBoostInProgress) {
final int wakefulness = getWakefulnessLocked();
final int wakeLockSummary = getWakeLockSummaryLocked();
if (wakefulness == WAKEFULNESS_ASLEEP || quiescent) {
@@ -398,13 +393,6 @@ public class PowerGroup {
// doze after screen off. This causes the screen off transition to be skipped.
}
- // It is important that POLICY_VR check happens after the wakefulness checks above so
- // that VR-mode does not prevent displays from transitioning to the correct state when
- // dozing or sleeping.
- if (vrModeEnabled) {
- return DisplayPowerRequest.POLICY_VR;
- }
-
if ((wakeLockSummary & WAKE_LOCK_SCREEN_BRIGHT) != 0
|| !bootCompleted
|| (getUserActivitySummaryLocked() & USER_ACTIVITY_SCREEN_BRIGHT) != 0
@@ -423,10 +411,10 @@ public class PowerGroup {
boolean useProximitySensor, boolean boostScreenBrightness, int dozeScreenState,
float dozeScreenBrightness, boolean overrideDrawWakeLock,
PowerSaveState powerSaverState, boolean quiescent, boolean dozeAfterScreenOff,
- boolean vrModeEnabled, boolean bootCompleted, boolean screenBrightnessBoostInProgress,
+ boolean bootCompleted, boolean screenBrightnessBoostInProgress,
boolean waitForNegativeProximity) {
mDisplayPowerRequest.policy = getDesiredScreenPolicyLocked(quiescent, dozeAfterScreenOff,
- vrModeEnabled, bootCompleted, screenBrightnessBoostInProgress);
+ bootCompleted, screenBrightnessBoostInProgress);
mDisplayPowerRequest.screenBrightnessOverride = screenBrightnessOverride;
mDisplayPowerRequest.useAutoBrightness = autoBrightness;
mDisplayPowerRequest.useProximitySensor = useProximitySensor;
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 1ea0988893ad..6e3c827e46f0 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -90,8 +90,6 @@ import android.os.WorkSource.WorkChain;
import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
import android.service.dreams.DreamManagerInternal;
-import android.service.vr.IVrManager;
-import android.service.vr.IVrStateCallbacks;
import android.sysprop.InitProperties;
import android.sysprop.PowerProperties;
import android.util.ArrayMap;
@@ -196,8 +194,6 @@ public final class PowerManagerService extends SystemService
private static final int DIRTY_SCREEN_BRIGHTNESS_BOOST = 1 << 11;
// Dirty bit: sQuiescent changed
private static final int DIRTY_QUIESCENT = 1 << 12;
- // Dirty bit: VR Mode enabled changed
- private static final int DIRTY_VR_MODE_CHANGED = 1 << 13;
// Dirty bit: attentive timer may have timed out
private static final int DIRTY_ATTENTIVE = 1 << 14;
// Dirty bit: display group wakefulness has changed
@@ -580,9 +576,6 @@ public final class PowerManagerService extends SystemService
public final float mScreenBrightnessDefault;
public final float mScreenBrightnessDoze;
public final float mScreenBrightnessDim;
- public final float mScreenBrightnessMinimumVr;
- public final float mScreenBrightnessMaximumVr;
- public final float mScreenBrightnessDefaultVr;
// Value we store for tracking face down behavior.
private boolean mIsFaceDown = false;
@@ -666,9 +659,6 @@ public final class PowerManagerService extends SystemService
// True if double tap to wake is enabled
private boolean mDoubleTapWakeEnabled;
- // True if we are currently in VR Mode.
- private boolean mIsVrModeEnabled;
-
// True if we in the process of performing a forceSuspend
private boolean mForceSuspendActive;
@@ -1145,29 +1135,6 @@ public final class PowerManagerService extends SystemService
mScreenBrightnessDim = dim;
}
- final float vrMin = mContext.getResources().getFloat(com.android.internal.R.dimen
- .config_screenBrightnessSettingForVrMinimumFloat);
- final float vrMax = mContext.getResources().getFloat(com.android.internal.R.dimen
- .config_screenBrightnessSettingForVrMaximumFloat);
- final float vrDef = mContext.getResources().getFloat(com.android.internal.R.dimen
- .config_screenBrightnessSettingForVrDefaultFloat);
- if (vrMin == INVALID_BRIGHTNESS_IN_CONFIG || vrMax == INVALID_BRIGHTNESS_IN_CONFIG
- || vrDef == INVALID_BRIGHTNESS_IN_CONFIG) {
- mScreenBrightnessMinimumVr = BrightnessSynchronizer.brightnessIntToFloat(
- mContext.getResources().getInteger(com.android.internal.R.integer
- .config_screenBrightnessForVrSettingMinimum));
- mScreenBrightnessMaximumVr = BrightnessSynchronizer.brightnessIntToFloat(
- mContext.getResources().getInteger(com.android.internal.R.integer
- .config_screenBrightnessForVrSettingMaximum));
- mScreenBrightnessDefaultVr = BrightnessSynchronizer.brightnessIntToFloat(
- mContext.getResources().getInteger(com.android.internal.R.integer
- .config_screenBrightnessForVrSettingDefault));
- } else {
- mScreenBrightnessMinimumVr = vrMin;
- mScreenBrightnessMaximumVr = vrMax;
- mScreenBrightnessDefaultVr = vrDef;
- }
-
synchronized (mLock) {
mBootingSuspendBlocker =
mInjector.createSuspendBlocker(this, "PowerManagerService.Booting");
@@ -1373,14 +1340,6 @@ public final class PowerManagerService extends SystemService
resolver.registerContentObserver(Settings.Global.getUriFor(
Settings.Global.DEVICE_DEMO_MODE),
false, mSettingsObserver, UserHandle.USER_SYSTEM);
- IVrManager vrManager = IVrManager.Stub.asInterface(getBinderService(Context.VR_SERVICE));
- if (vrManager != null) {
- try {
- vrManager.registerListener(mVrStateCallbacks);
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to register VR mode state listener: " + e);
- }
- }
// Register for broadcasts from other components of the system.
IntentFilter filter = new IntentFilter();
@@ -2848,7 +2807,7 @@ public final class PowerManagerService extends SystemService
>= powerGroup.getLastWakeTimeLocked()) {
groupNextTimeout = lastUserActivityTimeNoChangeLights + screenOffTimeout;
if (now < groupNextTimeout) {
- if (powerGroup.isPolicyBrightLocked() || powerGroup.isPolicyVrLocked()) {
+ if (powerGroup.isPolicyBrightLocked()) {
groupUserActivitySummary = USER_ACTIVITY_SCREEN_BRIGHT;
} else if (powerGroup.isPolicyDimLocked()) {
groupUserActivitySummary = USER_ACTIVITY_SCREEN_DIM;
@@ -3415,7 +3374,6 @@ public final class PowerManagerService extends SystemService
|| !mDreamsSupportedConfig
|| !mDreamsEnabledSetting
|| !(powerGroup.isBrightOrDimLocked())
- || powerGroup.isPolicyVrLocked()
|| (powerGroup.getUserActivitySummaryLocked() & (USER_ACTIVITY_SCREEN_BRIGHT
| USER_ACTIVITY_SCREEN_DIM | USER_ACTIVITY_SCREEN_DREAM)) == 0) {
return false;
@@ -3460,8 +3418,8 @@ public final class PowerManagerService extends SystemService
final boolean oldPowerGroupsReady = areAllPowerGroupsReadyLocked();
if ((dirty & (DIRTY_WAKE_LOCKS | DIRTY_USER_ACTIVITY | DIRTY_WAKEFULNESS
| DIRTY_ACTUAL_DISPLAY_POWER_STATE_UPDATED | DIRTY_BOOT_COMPLETED
- | DIRTY_SETTINGS | DIRTY_SCREEN_BRIGHTNESS_BOOST | DIRTY_VR_MODE_CHANGED |
- DIRTY_QUIESCENT | DIRTY_DISPLAY_GROUP_WAKEFULNESS)) != 0) {
+ | DIRTY_SETTINGS | DIRTY_SCREEN_BRIGHTNESS_BOOST
+ | DIRTY_QUIESCENT | DIRTY_DISPLAY_GROUP_WAKEFULNESS)) != 0) {
if ((dirty & DIRTY_QUIESCENT) != 0) {
if (areAllPowerGroupsReadyLocked()) {
sQuiescent = false;
@@ -3496,7 +3454,7 @@ public final class PowerManagerService extends SystemService
mDozeScreenBrightnessOverrideFromDreamManagerFloat,
mDrawWakeLockOverrideFromSidekick,
mBatterySaverPolicy.getBatterySaverPolicy(ServiceType.SCREEN_BRIGHTNESS),
- sQuiescent, mDozeAfterScreenOff, mIsVrModeEnabled, mBootCompleted,
+ sQuiescent, mDozeAfterScreenOff, mBootCompleted,
mScreenBrightnessBoostInProgress, mRequestWaitForNegativeProximity);
int wakefulness = powerGroup.getWakefulnessLocked();
if (DEBUG_SPEW) {
@@ -3514,7 +3472,6 @@ public final class PowerManagerService extends SystemService
+ ", useAutoBrightness=" + autoBrightness
+ ", mScreenBrightnessBoostInProgress="
+ mScreenBrightnessBoostInProgress
- + ", mIsVrModeEnabled= " + mIsVrModeEnabled
+ ", sQuiescent=" + sQuiescent);
}
@@ -3562,7 +3519,7 @@ public final class PowerManagerService extends SystemService
}
private boolean shouldBoostScreenBrightness() {
- return !mIsVrModeEnabled && mScreenBrightnessBoostInProgress;
+ return mScreenBrightnessBoostInProgress;
}
private static boolean isValidBrightness(float value) {
@@ -3573,7 +3530,7 @@ public final class PowerManagerService extends SystemService
@GuardedBy("mLock")
int getDesiredScreenPolicyLocked(int groupId) {
return mPowerGroups.get(groupId).getDesiredScreenPolicyLocked(sQuiescent,
- mDozeAfterScreenOff, mIsVrModeEnabled, mBootCompleted,
+ mDozeAfterScreenOff, mBootCompleted,
mScreenBrightnessBoostInProgress);
}
@@ -3654,8 +3611,7 @@ public final class PowerManagerService extends SystemService
@GuardedBy("mLock")
private boolean shouldUseProximitySensorLocked() {
// Use default display group for proximity sensor.
- return !mIsVrModeEnabled
- && (mPowerGroups.get(Display.DEFAULT_DISPLAY_GROUP).getWakeLockSummaryLocked()
+ return (mPowerGroups.get(Display.DEFAULT_DISPLAY_GROUP).getWakeLockSummaryLocked()
& WAKE_LOCK_PROXIMITY_SCREEN_OFF) != 0;
}
@@ -4279,11 +4235,6 @@ public final class PowerManagerService extends SystemService
}
}
- @VisibleForTesting
- void setVrModeEnabled(boolean enabled) {
- mIsVrModeEnabled = enabled;
- }
-
private void setPowerBoostInternal(int boost, int durationMs) {
// Maybe filter the event.
mNativeWrapper.nativeSetPowerBoost(boost, durationMs);
@@ -4553,7 +4504,6 @@ public final class PowerManagerService extends SystemService
pw.println(" mScreenBrightnessMaximum=" + mScreenBrightnessMaximum);
pw.println(" mScreenBrightnessDefault=" + mScreenBrightnessDefault);
pw.println(" mDoubleTapWakeEnabled=" + mDoubleTapWakeEnabled);
- pw.println(" mIsVrModeEnabled=" + mIsVrModeEnabled);
pw.println(" mForegroundProfile=" + mForegroundProfile);
pw.println(" mUserId=" + mUserId);
@@ -4964,9 +4914,6 @@ public final class PowerManagerService extends SystemService
proto.write(
PowerServiceSettingsAndConfigurationDumpProto.IS_DOUBLE_TAP_WAKE_ENABLED,
mDoubleTapWakeEnabled);
- proto.write(
- PowerServiceSettingsAndConfigurationDumpProto.IS_VR_MODE_ENABLED,
- mIsVrModeEnabled);
proto.end(settingsAndConfigurationToken);
final long attentiveTimeout = getAttentiveTimeoutLocked();
@@ -5095,21 +5042,6 @@ public final class PowerManagerService extends SystemService
}
}
- private final IVrStateCallbacks mVrStateCallbacks = new IVrStateCallbacks.Stub() {
- @Override
- public void onVrStateChanged(boolean enabled) {
- setPowerModeInternal(Mode.VR, enabled);
-
- synchronized (mLock) {
- if (mIsVrModeEnabled != enabled) {
- setVrModeEnabled(enabled);
- mDirty |= DIRTY_VR_MODE_CHANGED;
- updatePowerStateLocked();
- }
- }
- }
- };
-
private final AmbientDisplaySuppressionChangedCallback mAmbientSuppressionChangedCallback =
new AmbientDisplaySuppressionChangedCallback() {
@Override
@@ -5837,12 +5769,6 @@ public final class PowerManagerService extends SystemService
return mScreenBrightnessDim;
case PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DOZE:
return mScreenBrightnessDoze;
- case PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM_VR:
- return mScreenBrightnessMinimumVr;
- case PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM_VR:
- return mScreenBrightnessMaximumVr;
- case PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DEFAULT_VR:
- return mScreenBrightnessDefaultVr;
default:
return PowerManager.BRIGHTNESS_INVALID_FLOAT;
}
@@ -6626,7 +6552,6 @@ public final class PowerManagerService extends SystemService
case Display.STATE_DOZE_SUSPEND:
case Display.STATE_ON_SUSPEND:
case Display.STATE_ON:
- case Display.STATE_VR:
break;
default:
screenState = Display.STATE_UNKNOWN;
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index 0c5e451a93cc..af4fa85ba5a3 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -12229,6 +12229,7 @@ public class BatteryStatsImpl extends BatteryStats {
@GuardedBy("this")
private void incrementPerRatDataLocked(ModemActivityInfo deltaInfo, long elapsedRealtimeMs) {
final int infoSize = deltaInfo.getSpecificInfoLength();
+
if (infoSize == 1 && deltaInfo.getSpecificInfoRat(0)
== AccessNetworkConstants.AccessNetworkType.UNKNOWN
&& deltaInfo.getSpecificInfoFrequencyRange(0)
diff --git a/services/core/java/com/android/server/power/stats/MobileRadioPowerCalculator.java b/services/core/java/com/android/server/power/stats/MobileRadioPowerCalculator.java
index 806ed64597a8..2c7aea9b5878 100644
--- a/services/core/java/com/android/server/power/stats/MobileRadioPowerCalculator.java
+++ b/services/core/java/com/android/server/power/stats/MobileRadioPowerCalculator.java
@@ -15,45 +15,79 @@
*/
package com.android.server.power.stats;
+import android.annotation.Nullable;
import android.os.BatteryConsumer;
import android.os.BatteryStats;
import android.os.BatteryUsageStats;
import android.os.BatteryUsageStatsQuery;
import android.os.UidBatteryConsumer;
import android.telephony.CellSignalStrength;
+import android.telephony.ServiceState;
import android.util.Log;
+import android.util.LongArrayQueue;
import android.util.SparseArray;
import com.android.internal.os.PowerProfile;
+import com.android.internal.power.ModemPowerProfile;
+
+import java.util.ArrayList;
public class MobileRadioPowerCalculator extends PowerCalculator {
private static final String TAG = "MobRadioPowerCalculator";
private static final boolean DEBUG = PowerCalculator.DEBUG;
+ private static final double MILLIS_IN_HOUR = 1000.0 * 60 * 60;
+
private static final int NUM_SIGNAL_STRENGTH_LEVELS =
CellSignalStrength.getNumSignalStrengthLevels();
private static final BatteryConsumer.Key[] UNINITIALIZED_KEYS = new BatteryConsumer.Key[0];
+ private static final int IGNORE = -1;
- private final UsageBasedPowerEstimator mActivePowerEstimator;
+ private final UsageBasedPowerEstimator mActivePowerEstimator; // deprecated
private final UsageBasedPowerEstimator[] mIdlePowerEstimators =
- new UsageBasedPowerEstimator[NUM_SIGNAL_STRENGTH_LEVELS];
- private final UsageBasedPowerEstimator mScanPowerEstimator;
+ new UsageBasedPowerEstimator[NUM_SIGNAL_STRENGTH_LEVELS]; // deprecated
+ private final UsageBasedPowerEstimator mScanPowerEstimator; // deprecated
+
+ @Nullable
+ private final UsageBasedPowerEstimator mSleepPowerEstimator;
+ @Nullable
+ private final UsageBasedPowerEstimator mIdlePowerEstimator;
+
+ private final PowerProfile mPowerProfile;
private static class PowerAndDuration {
- public long durationMs;
+ public long remainingDurationMs;
public double remainingPowerMah;
public long totalAppDurationMs;
public double totalAppPowerMah;
- public long signalDurationMs;
- public long noCoverageDurationMs;
}
public MobileRadioPowerCalculator(PowerProfile profile) {
- // Power consumption when radio is active
+ mPowerProfile = profile;
+
+ final double sleepDrainRateMa = mPowerProfile.getAverageBatteryDrainOrDefaultMa(
+ PowerProfile.SUBSYSTEM_MODEM | ModemPowerProfile.MODEM_DRAIN_TYPE_SLEEP,
+ Double.NaN);
+ if (Double.isNaN(sleepDrainRateMa)) {
+ mSleepPowerEstimator = null;
+ } else {
+ mSleepPowerEstimator = new UsageBasedPowerEstimator(sleepDrainRateMa);
+ }
+
+ final double idleDrainRateMa = mPowerProfile.getAverageBatteryDrainOrDefaultMa(
+ PowerProfile.SUBSYSTEM_MODEM | ModemPowerProfile.MODEM_DRAIN_TYPE_IDLE,
+ Double.NaN);
+ if (Double.isNaN(idleDrainRateMa)) {
+ mIdlePowerEstimator = null;
+ } else {
+ mIdlePowerEstimator = new UsageBasedPowerEstimator(idleDrainRateMa);
+ }
+
+ // Instantiate legacy power estimators
double powerRadioActiveMa =
- profile.getAveragePowerOrDefault(PowerProfile.POWER_RADIO_ACTIVE, -1);
- if (powerRadioActiveMa == -1) {
+ profile.getAveragePowerOrDefault(PowerProfile.POWER_RADIO_ACTIVE, Double.NaN);
+ if (Double.isNaN(powerRadioActiveMa)) {
double sum = 0;
sum += profile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_RX);
for (int i = 0; i < NUM_SIGNAL_STRENGTH_LEVELS; i++) {
@@ -61,11 +95,10 @@ public class MobileRadioPowerCalculator extends PowerCalculator {
}
powerRadioActiveMa = sum / (NUM_SIGNAL_STRENGTH_LEVELS + 1);
}
-
mActivePowerEstimator = new UsageBasedPowerEstimator(powerRadioActiveMa);
- // Power consumption when radio is on, but idle
- if (profile.getAveragePowerOrDefault(PowerProfile.POWER_RADIO_ON, -1) != -1) {
+ if (!Double.isNaN(
+ profile.getAveragePowerOrDefault(PowerProfile.POWER_RADIO_ON, Double.NaN))) {
for (int i = 0; i < NUM_SIGNAL_STRENGTH_LEVELS; i++) {
mIdlePowerEstimators[i] = new UsageBasedPowerEstimator(
profile.getAveragePower(PowerProfile.POWER_RADIO_ON, i));
@@ -95,6 +128,23 @@ public class MobileRadioPowerCalculator extends PowerCalculator {
PowerAndDuration total = new PowerAndDuration();
+ final long totalConsumptionUC = batteryStats.getMobileRadioMeasuredBatteryConsumptionUC();
+ final int powerModel = getPowerModel(totalConsumptionUC, query);
+
+ final double totalActivePowerMah;
+ final ArrayList<UidBatteryConsumer.Builder> apps;
+ final LongArrayQueue appDurationsMs;
+ if (powerModel == BatteryConsumer.POWER_MODEL_MEASURED_ENERGY) {
+ // Measured energy is available, don't bother calculating power.
+ totalActivePowerMah = Double.NaN;
+ apps = null;
+ appDurationsMs = null;
+ } else {
+ totalActivePowerMah = calculateActiveModemPowerMah(batteryStats, rawRealtimeUs);
+ apps = new ArrayList();
+ appDurationsMs = new LongArrayQueue();
+ }
+
final SparseArray<UidBatteryConsumer.Builder> uidBatteryConsumerBuilders =
builder.getUidBatteryConsumerBuilders();
BatteryConsumer.Key[] keys = UNINITIALIZED_KEYS;
@@ -110,132 +160,352 @@ public class MobileRadioPowerCalculator extends PowerCalculator {
}
}
- calculateApp(app, uid, total, query, keys);
+ // Sum and populate each app's active radio duration.
+ final long radioActiveDurationMs = calculateDuration(uid,
+ BatteryStats.STATS_SINCE_CHARGED);
+ if (!app.isVirtualUid()) {
+ total.totalAppDurationMs += radioActiveDurationMs;
+ }
+ app.setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
+ radioActiveDurationMs);
+
+ if (powerModel == BatteryConsumer.POWER_MODEL_MEASURED_ENERGY) {
+ // Measured energy is available, populate the consumed power now.
+ final long appConsumptionUC = uid.getMobileRadioMeasuredBatteryConsumptionUC();
+ if (appConsumptionUC != BatteryStats.POWER_DATA_UNAVAILABLE) {
+ final double appConsumptionMah = uCtoMah(appConsumptionUC);
+ if (!app.isVirtualUid()) {
+ total.totalAppPowerMah += appConsumptionMah;
+ }
+ app.setConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
+ appConsumptionMah, powerModel);
+
+ if (query.isProcessStateDataNeeded() && keys != null) {
+ for (BatteryConsumer.Key key : keys) {
+ final int processState = key.processState;
+ if (processState == BatteryConsumer.PROCESS_STATE_UNSPECIFIED) {
+ // Already populated with the total across all process states
+ continue;
+ }
+ final long consumptionInStateUc =
+ uid.getMobileRadioMeasuredBatteryConsumptionUC(processState);
+ final double powerInStateMah = uCtoMah(consumptionInStateUc);
+ app.setConsumedPower(key, powerInStateMah, powerModel);
+ }
+ }
+ }
+ } else {
+ // Cache the app and its active duration for later calculations.
+ apps.add(app);
+ appDurationsMs.addLast(radioActiveDurationMs);
+ }
+ }
+
+ long totalActiveDurationMs = batteryStats.getMobileRadioActiveTime(rawRealtimeUs,
+ BatteryStats.STATS_SINCE_CHARGED) / 1000;
+ if (totalActiveDurationMs < total.totalAppDurationMs) {
+ totalActiveDurationMs = total.totalAppDurationMs;
}
- final long totalConsumptionUC = batteryStats.getMobileRadioMeasuredBatteryConsumptionUC();
- final int powerModel = getPowerModel(totalConsumptionUC, query);
- calculateRemaining(total, powerModel, batteryStats, rawRealtimeUs, totalConsumptionUC);
+ if (powerModel != BatteryConsumer.POWER_MODEL_MEASURED_ENERGY) {
+ // Need to smear the calculated total active power across the apps based on app
+ // active durations.
+ final int appSize = apps.size();
+ for (int i = 0; i < appSize; i++) {
+ final UidBatteryConsumer.Builder app = apps.get(i);
+ final long activeDurationMs = appDurationsMs.get(i);
+
+ // Proportionally attribute radio power consumption based on active duration.
+ final double appConsumptionMah;
+ if (totalActiveDurationMs == 0.0) {
+ appConsumptionMah = 0.0;
+ } else {
+ appConsumptionMah =
+ (totalActivePowerMah * activeDurationMs) / totalActiveDurationMs;
+ }
+
+ if (!app.isVirtualUid()) {
+ total.totalAppPowerMah += appConsumptionMah;
+ }
+ app.setConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
+ appConsumptionMah, powerModel);
+
+ if (query.isProcessStateDataNeeded() && keys != null) {
+ final BatteryStats.Uid uid = app.getBatteryStatsUid();
+ for (BatteryConsumer.Key key : keys) {
+ final int processState = key.processState;
+ if (processState == BatteryConsumer.PROCESS_STATE_UNSPECIFIED) {
+ // Already populated with the total across all process states
+ continue;
+ }
+
+ final long durationInStateMs =
+ uid.getMobileRadioActiveTimeInProcessState(processState) / 1000;
+ // Proportionally attribute per process state radio power consumption
+ // based on time state duration.
+ final double powerInStateMah;
+ if (activeDurationMs == 0.0) {
+ powerInStateMah = 0.0;
+ } else {
+ powerInStateMah =
+ (appConsumptionMah * durationInStateMs) / activeDurationMs;
+ }
+ app.setConsumedPower(key, powerInStateMah, powerModel);
+ }
+ }
+ }
+ }
+
+ total.remainingDurationMs = totalActiveDurationMs - total.totalAppDurationMs;
+
+ // Calculate remaining power consumption.
+ if (powerModel == BatteryConsumer.POWER_MODEL_MEASURED_ENERGY) {
+ total.remainingPowerMah = uCtoMah(totalConsumptionUC) - total.totalAppPowerMah;
+ if (total.remainingPowerMah < 0) total.remainingPowerMah = 0;
+ } else {
+ // Smear unattributed active time and add it to the remaining power consumption.
+ total.remainingPowerMah +=
+ (totalActivePowerMah * total.remainingDurationMs) / totalActiveDurationMs;
+
+ // Calculate the inactive modem power consumption.
+ final BatteryStats.ControllerActivityCounter modemActivity =
+ batteryStats.getModemControllerActivity();
+ if (modemActivity != null && (mSleepPowerEstimator != null
+ || mIdlePowerEstimator != null)) {
+ final long sleepDurationMs = modemActivity.getSleepTimeCounter().getCountLocked(
+ BatteryStats.STATS_SINCE_CHARGED);
+ total.remainingPowerMah += mSleepPowerEstimator.calculatePower(sleepDurationMs);
+ final long idleDurationMs = modemActivity.getIdleTimeCounter().getCountLocked(
+ BatteryStats.STATS_SINCE_CHARGED);
+ total.remainingPowerMah += mIdlePowerEstimator.calculatePower(idleDurationMs);
+ } else {
+ // Modem activity counters unavailable. Use legacy calculations for inactive usage.
+ final long scanningTimeMs = batteryStats.getPhoneSignalScanningTime(rawRealtimeUs,
+ BatteryStats.STATS_SINCE_CHARGED) / 1000;
+ total.remainingPowerMah += calcScanTimePowerMah(scanningTimeMs);
+ for (int i = 0; i < NUM_SIGNAL_STRENGTH_LEVELS; i++) {
+ long strengthTimeMs = batteryStats.getPhoneSignalStrengthTime(i, rawRealtimeUs,
+ BatteryStats.STATS_SINCE_CHARGED) / 1000;
+ total.remainingPowerMah += calcIdlePowerAtSignalStrengthMah(strengthTimeMs, i);
+ }
+ }
+
+ }
if (total.remainingPowerMah != 0 || total.totalAppPowerMah != 0) {
builder.getAggregateBatteryConsumerBuilder(
- BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
+ BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
.setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
- total.durationMs)
+ total.remainingDurationMs + total.totalAppDurationMs)
.setConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
total.remainingPowerMah + total.totalAppPowerMah, powerModel);
builder.getAggregateBatteryConsumerBuilder(
- BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
+ BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
.setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
- total.durationMs)
+ total.totalAppDurationMs)
.setConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
total.totalAppPowerMah, powerModel);
}
}
- private void calculateApp(UidBatteryConsumer.Builder app, BatteryStats.Uid u,
- PowerAndDuration total,
- BatteryUsageStatsQuery query, BatteryConsumer.Key[] keys) {
- final long radioActiveDurationMs = calculateDuration(u, BatteryStats.STATS_SINCE_CHARGED);
- final long consumptionUC = u.getMobileRadioMeasuredBatteryConsumptionUC();
- final int powerModel = getPowerModel(consumptionUC, query);
- final double powerMah = calculatePower(u, powerModel, radioActiveDurationMs, consumptionUC);
-
- if (!app.isVirtualUid()) {
- total.totalAppDurationMs += radioActiveDurationMs;
- total.totalAppPowerMah += powerMah;
+ private long calculateDuration(BatteryStats.Uid u, int statsType) {
+ return u.getMobileRadioActiveTime(statsType) / 1000;
+ }
+
+ private double calculateActiveModemPowerMah(BatteryStats bs, long elapsedRealtimeUs) {
+ final long elapsedRealtimeMs = elapsedRealtimeUs / 1000;
+ final int txLvlCount = CellSignalStrength.getNumSignalStrengthLevels();
+ double consumptionMah = 0.0;
+
+ if (DEBUG) {
+ Log.d(TAG, "Calculating radio power consumption at elapased real timestamp : "
+ + elapsedRealtimeMs + " ms");
}
- app.setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
- radioActiveDurationMs)
- .setConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO, powerMah,
- powerModel);
+ boolean hasConstants = false;
+
+ for (int rat = 0; rat < BatteryStats.RADIO_ACCESS_TECHNOLOGY_COUNT; rat++) {
+ final int freqCount = rat == BatteryStats.RADIO_ACCESS_TECHNOLOGY_NR
+ ? ServiceState.FREQUENCY_RANGE_COUNT : 1;
+ for (int freq = 0; freq < freqCount; freq++) {
+ for (int txLvl = 0; txLvl < txLvlCount; txLvl++) {
+ final long txDurationMs = bs.getActiveTxRadioDurationMs(rat, freq, txLvl,
+ elapsedRealtimeMs);
+ if (txDurationMs == BatteryStats.DURATION_UNAVAILABLE) {
+ continue;
+ }
+ final double txConsumptionMah = calcTxStatePowerMah(rat, freq, txLvl,
+ txDurationMs);
+ if (Double.isNaN(txConsumptionMah)) {
+ continue;
+ }
+ hasConstants = true;
+ consumptionMah += txConsumptionMah;
+ }
- if (query.isProcessStateDataNeeded() && keys != null) {
- for (BatteryConsumer.Key key: keys) {
- final int processState = key.processState;
- if (processState == BatteryConsumer.PROCESS_STATE_UNSPECIFIED) {
- // Already populated with the total across all process states
+ final long rxDurationMs = bs.getActiveRxRadioDurationMs(rat, freq,
+ elapsedRealtimeMs);
+ if (rxDurationMs == BatteryStats.DURATION_UNAVAILABLE) {
+ continue;
+ }
+ final double rxConsumptionMah = calcRxStatePowerMah(rat, freq, rxDurationMs);
+ if (Double.isNaN(rxConsumptionMah)) {
continue;
}
+ hasConstants = true;
+ consumptionMah += rxConsumptionMah;
+ }
+ }
- final long durationInStateMs =
- u.getMobileRadioActiveTimeInProcessState(processState) / 1000;
- final long consumptionInStateUc =
- u.getMobileRadioMeasuredBatteryConsumptionUC(processState);
- final double powerInStateMah = calculatePower(u, powerModel, durationInStateMs,
- consumptionInStateUc);
- app.setConsumedPower(key, powerInStateMah, powerModel);
+ if (!hasConstants) {
+ final long radioActiveDurationMs = bs.getMobileRadioActiveTime(elapsedRealtimeUs,
+ BatteryStats.STATS_SINCE_CHARGED) / 1000;
+ if (DEBUG) {
+ Log.d(TAG,
+ "Failed to calculate radio power consumption. Reattempted with legacy "
+ + "method. Radio active duration : "
+ + radioActiveDurationMs + " ms");
+ }
+ if (radioActiveDurationMs > 0) {
+ consumptionMah = calcPowerFromRadioActiveDurationMah(radioActiveDurationMs);
+ } else {
+ consumptionMah = 0.0;
}
}
- }
- private long calculateDuration(BatteryStats.Uid u, int statsType) {
- return u.getMobileRadioActiveTime(statsType) / 1000;
+ if (DEBUG) {
+ Log.d(TAG, "Total active radio power consumption calculated to be " + consumptionMah
+ + " mAH.");
+ }
+
+ return consumptionMah;
}
- private double calculatePower(BatteryStats.Uid u, @BatteryConsumer.PowerModel int powerModel,
- long radioActiveDurationMs, long measuredChargeUC) {
- if (powerModel == BatteryConsumer.POWER_MODEL_MEASURED_ENERGY) {
- return uCtoMah(measuredChargeUC);
+ private static long buildModemPowerProfileKey(@ModemPowerProfile.ModemDrainType int drainType,
+ @BatteryStats.RadioAccessTechnology int rat, @ServiceState.FrequencyRange int freqRange,
+ int txLevel) {
+ long key = PowerProfile.SUBSYSTEM_MODEM;
+
+ // Attach Modem drain type to the key if specified.
+ if (drainType != IGNORE) {
+ key |= drainType;
}
- if (radioActiveDurationMs > 0) {
- return calcPowerFromRadioActiveDurationMah(radioActiveDurationMs);
+ // Attach RadioAccessTechnology to the key if specified.
+ switch (rat) {
+ case IGNORE:
+ // do nothing
+ break;
+ case BatteryStats.RADIO_ACCESS_TECHNOLOGY_OTHER:
+ key |= ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT;
+ break;
+ case BatteryStats.RADIO_ACCESS_TECHNOLOGY_LTE:
+ key |= ModemPowerProfile.MODEM_RAT_TYPE_LTE;
+ break;
+ case BatteryStats.RADIO_ACCESS_TECHNOLOGY_NR:
+ key |= ModemPowerProfile.MODEM_RAT_TYPE_NR;
+ break;
+ default:
+ Log.w(TAG, "Unexpected RadioAccessTechnology : " + rat);
}
- return 0;
- }
- private void calculateRemaining(PowerAndDuration total,
- @BatteryConsumer.PowerModel int powerModel, BatteryStats batteryStats,
- long rawRealtimeUs, long totalConsumptionUC) {
- long signalTimeMs = 0;
- double powerMah = 0;
+ // Attach NR Frequency Range to the key if specified.
+ switch (freqRange) {
+ case IGNORE:
+ // do nothing
+ break;
+ case ServiceState.FREQUENCY_RANGE_UNKNOWN:
+ key |= ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_DEFAULT;
+ break;
+ case ServiceState.FREQUENCY_RANGE_LOW:
+ key |= ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_LOW;
+ break;
+ case ServiceState.FREQUENCY_RANGE_MID:
+ key |= ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MID;
+ break;
+ case ServiceState.FREQUENCY_RANGE_HIGH:
+ key |= ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_HIGH;
+ break;
+ case ServiceState.FREQUENCY_RANGE_MMWAVE:
+ key |= ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MMWAVE;
+ break;
+ default:
+ Log.w(TAG, "Unexpected NR frequency range : " + freqRange);
+ }
- if (powerModel == BatteryConsumer.POWER_MODEL_MEASURED_ENERGY) {
- powerMah = uCtoMah(totalConsumptionUC) - total.totalAppPowerMah;
- if (powerMah < 0) powerMah = 0;
+ // Attach transmission level to the key if specified.
+ switch (txLevel) {
+ case IGNORE:
+ // do nothing
+ break;
+ case 0:
+ key |= ModemPowerProfile.MODEM_TX_LEVEL_0;
+ break;
+ case 1:
+ key |= ModemPowerProfile.MODEM_TX_LEVEL_1;
+ break;
+ case 2:
+ key |= ModemPowerProfile.MODEM_TX_LEVEL_2;
+ break;
+ case 3:
+ key |= ModemPowerProfile.MODEM_TX_LEVEL_3;
+ break;
+ case 4:
+ key |= ModemPowerProfile.MODEM_TX_LEVEL_4;
+ break;
+ default:
+ Log.w(TAG, "Unexpected transmission level : " + txLevel);
}
+ return key;
+ }
- for (int i = 0; i < NUM_SIGNAL_STRENGTH_LEVELS; i++) {
- long strengthTimeMs = batteryStats.getPhoneSignalStrengthTime(i, rawRealtimeUs,
- BatteryStats.STATS_SINCE_CHARGED) / 1000;
- if (powerModel == BatteryConsumer.POWER_MODEL_POWER_PROFILE) {
- final double p = calcIdlePowerAtSignalStrengthMah(strengthTimeMs, i);
- if (DEBUG && p != 0) {
- Log.d(TAG, "Cell strength #" + i + ": time=" + strengthTimeMs + " power="
- + BatteryStats.formatCharge(p));
- }
- powerMah += p;
- }
- signalTimeMs += strengthTimeMs;
- if (i == 0) {
- total.noCoverageDurationMs = strengthTimeMs;
- }
+ /**
+ * Calculates active receive radio power consumption (in milliamp-hours) from the given state's
+ * duration.
+ */
+ public double calcRxStatePowerMah(@BatteryStats.RadioAccessTechnology int rat,
+ @ServiceState.FrequencyRange int freqRange, long rxDurationMs) {
+ final long rxKey = buildModemPowerProfileKey(ModemPowerProfile.MODEM_DRAIN_TYPE_RX, rat,
+ freqRange, IGNORE);
+ final double drainRateMa = mPowerProfile.getAverageBatteryDrainOrDefaultMa(rxKey,
+ Double.NaN);
+ if (Double.isNaN(drainRateMa)) {
+ Log.w(TAG, "Unavailable Power Profile constant for key 0x" + Long.toHexString(rxKey));
+ return Double.NaN;
}
- final long scanningTimeMs = batteryStats.getPhoneSignalScanningTime(rawRealtimeUs,
- BatteryStats.STATS_SINCE_CHARGED) / 1000;
- long radioActiveTimeMs = batteryStats.getMobileRadioActiveTime(rawRealtimeUs,
- BatteryStats.STATS_SINCE_CHARGED) / 1000;
- long remainingActiveTimeMs = radioActiveTimeMs - total.totalAppDurationMs;
+ final double consumptionMah = drainRateMa * rxDurationMs / MILLIS_IN_HOUR;
+ if (DEBUG) {
+ Log.d(TAG, "Calculated RX consumption " + consumptionMah + " mAH from a drain rate of "
+ + drainRateMa + " mA and a duration of " + rxDurationMs + " ms for "
+ + ModemPowerProfile.keyToString((int) rxKey));
+ }
+ return consumptionMah;
+ }
- if (powerModel == BatteryConsumer.POWER_MODEL_POWER_PROFILE) {
- final double p = calcScanTimePowerMah(scanningTimeMs);
- if (DEBUG && p != 0) {
- Log.d(TAG, "Cell radio scanning: time=" + scanningTimeMs
- + " power=" + BatteryStats.formatCharge(p));
- }
- powerMah += p;
+ /**
+ * Calculates active transmit radio power consumption (in milliamp-hours) from the given state's
+ * duration.
+ */
+ public double calcTxStatePowerMah(@BatteryStats.RadioAccessTechnology int rat,
+ @ServiceState.FrequencyRange int freqRange, int txLevel, long txDurationMs) {
+ final long txKey = buildModemPowerProfileKey(ModemPowerProfile.MODEM_DRAIN_TYPE_TX, rat,
+ freqRange, txLevel);
+ final double drainRateMa = mPowerProfile.getAverageBatteryDrainOrDefaultMa(txKey,
+ Double.NaN);
+ if (Double.isNaN(drainRateMa)) {
+ Log.w(TAG, "Unavailable Power Profile constant for key 0x" + Long.toHexString(txKey));
+ return Double.NaN;
+ }
- if (remainingActiveTimeMs > 0) {
- powerMah += calcPowerFromRadioActiveDurationMah(remainingActiveTimeMs);
- }
+ final double consumptionMah = drainRateMa * txDurationMs / MILLIS_IN_HOUR;
+ if (DEBUG) {
+ Log.d(TAG, "Calculated TX consumption " + consumptionMah + " mAH from a drain rate of "
+ + drainRateMa + " mA and a duration of " + txDurationMs + " ms for "
+ + ModemPowerProfile.keyToString((int) txKey));
}
- total.durationMs = radioActiveTimeMs;
- total.remainingPowerMah = powerMah;
- total.signalDurationMs = signalTimeMs;
+ return consumptionMah;
}
/**
diff --git a/services/core/java/com/android/server/security/rkp/OWNERS b/services/core/java/com/android/server/security/rkp/OWNERS
new file mode 100644
index 000000000000..348f94048311
--- /dev/null
+++ b/services/core/java/com/android/server/security/rkp/OWNERS
@@ -0,0 +1 @@
+file:platform/frameworks/base:master:/core/java/android/security/rkp/OWNERS
diff --git a/services/core/java/com/android/server/security/rkp/RemoteProvisioningService.java b/services/core/java/com/android/server/security/rkp/RemoteProvisioningService.java
new file mode 100644
index 000000000000..65a4b38629b6
--- /dev/null
+++ b/services/core/java/com/android/server/security/rkp/RemoteProvisioningService.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.security.rkp;
+
+import android.content.Context;
+import android.os.Binder;
+import android.os.OutcomeReceiver;
+import android.os.RemoteException;
+import android.security.rkp.IGetKeyCallback;
+import android.security.rkp.IGetRegistrationCallback;
+import android.security.rkp.IRegistration;
+import android.security.rkp.IRemoteProvisioning;
+import android.security.rkp.service.RegistrationProxy;
+import android.util.Log;
+
+import com.android.server.SystemService;
+
+import java.time.Duration;
+
+/**
+ * Implements the remote provisioning system service. This service is backed by a mainline
+ * module, allowing the underlying implementation to be updated. The code here is a thin
+ * proxy for the code in android.security.rkp.service.
+ *
+ * @hide
+ */
+public class RemoteProvisioningService extends SystemService {
+ public static final String TAG = "RemoteProvisionSysSvc";
+ private static final Duration CREATE_REGISTRATION_TIMEOUT = Duration.ofSeconds(10);
+ private final RemoteProvisioningImpl mBinderImpl = new RemoteProvisioningImpl();
+
+ /** @hide */
+ public RemoteProvisioningService(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void onStart() {
+ publishBinderService(Context.REMOTE_PROVISIONING_SERVICE, mBinderImpl);
+ }
+
+ private final class RemoteProvisioningImpl extends IRemoteProvisioning.Stub {
+
+ final class RegistrationBinder extends IRegistration.Stub {
+ static final String TAG = RemoteProvisioningService.TAG;
+ private final RegistrationProxy mRegistration;
+
+ RegistrationBinder(RegistrationProxy registration) {
+ mRegistration = registration;
+ }
+
+ @Override
+ public void getKey(int keyId, IGetKeyCallback callback) {
+ Log.e(TAG, "RegistrationBinder.getKey NOT YET IMPLEMENTED");
+ }
+
+ @Override
+ public void cancelGetKey(IGetKeyCallback callback) {
+ Log.e(TAG, "RegistrationBinder.cancelGetKey NOT YET IMPLEMENTED");
+ }
+
+ @Override
+ public void storeUpgradedKey(byte[] oldKeyBlob, byte[] newKeyBlob) {
+ Log.e(TAG, "RegistrationBinder.storeUpgradedKey NOT YET IMPLEMENTED");
+ }
+ }
+
+ @Override
+ public void getRegistration(String irpcName, IGetRegistrationCallback callback)
+ throws RemoteException {
+ final int callerUid = Binder.getCallingUidOrThrow();
+ final long callingIdentity = Binder.clearCallingIdentity();
+ try {
+ Log.i(TAG, "getRegistration(" + irpcName + ")");
+ RegistrationProxy.createAsync(
+ getContext(),
+ callerUid,
+ irpcName,
+ CREATE_REGISTRATION_TIMEOUT,
+ getContext().getMainExecutor(),
+ new OutcomeReceiver<>() {
+ @Override
+ public void onResult(RegistrationProxy registration) {
+ try {
+ callback.onSuccess(new RegistrationBinder(registration));
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling success callback", e);
+ }
+ }
+
+ @Override
+ public void onError(Exception error) {
+ try {
+ callback.onError(error.toString());
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling error callback", e);
+ }
+ }
+ });
+ } finally {
+ Binder.restoreCallingIdentity(callingIdentity);
+ }
+ }
+
+ @Override
+ public void cancelGetRegistration(IGetRegistrationCallback callback)
+ throws RemoteException {
+ Log.i(TAG, "cancelGetRegistration()");
+ callback.onError("cancelGetRegistration not yet implemented");
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
index 434cd78f137b..392fda9520fe 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
@@ -205,4 +205,16 @@ public interface StatusBarManagerInternal {
* @see com.android.internal.statusbar.IStatusBar#showRearDisplayDialog
*/
void showRearDisplayDialog(int currentBaseState);
+
+ /**
+ * Called when requested to go to fullscreen from the active split app.
+ */
+ void goToFullscreenFromSplit();
+
+ /**
+ * Enters stage split from a current running app.
+ *
+ * @see com.android.internal.statusbar.IStatusBar#enterStageSplitFromRunningApp
+ */
+ void enterStageSplitFromRunningApp(boolean leftOrTop);
}
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 006d88887a87..8d71d9cc6dc8 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -726,6 +726,24 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
} catch (RemoteException ex) { }
}
}
+
+ @Override
+ public void goToFullscreenFromSplit() {
+ if (mBar != null) {
+ try {
+ mBar.goToFullscreenFromSplit();
+ } catch (RemoteException ex) { }
+ }
+ }
+
+ @Override
+ public void enterStageSplitFromRunningApp(boolean leftOrTop) {
+ if (mBar != null) {
+ try {
+ mBar.enterStageSplitFromRunningApp(leftOrTop);
+ } catch (RemoteException ex) { }
+ }
+ }
};
private final GlobalActionsProvider mGlobalActionsProvider = new GlobalActionsProvider() {
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 5d084616bfea..4480d521323e 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -34,6 +34,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
import android.annotation.NonNull;
import android.app.ActivityManager;
+import android.app.ActivityOptions;
import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.ILocalWallpaperColorConsumer;
@@ -3166,6 +3167,15 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
}
}
+ final ActivityOptions clientOptions = ActivityOptions.makeBasic();
+ clientOptions.setIgnorePendingIntentCreatorForegroundState(true);
+ PendingIntent clientIntent = PendingIntent.getActivityAsUser(
+ mContext, 0, Intent.createChooser(
+ new Intent(Intent.ACTION_SET_WALLPAPER),
+ mContext.getText(com.android.internal.R.string.chooser_wallpaper)),
+ PendingIntent.FLAG_IMMUTABLE, clientOptions.toBundle(),
+ UserHandle.of(serviceUserId));
+
// Bind the service!
if (DEBUG) Slog.v(TAG, "Binding to:" + componentName);
final int componentUid = mIPackageManager.getPackageUid(componentName.getPackageName(),
@@ -3174,11 +3184,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
intent.setComponent(componentName);
intent.putExtra(Intent.EXTRA_CLIENT_LABEL,
com.android.internal.R.string.wallpaper_binding_label);
- intent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivityAsUser(
- mContext, 0,
- Intent.createChooser(new Intent(Intent.ACTION_SET_WALLPAPER),
- mContext.getText(com.android.internal.R.string.chooser_wallpaper)),
- PendingIntent.FLAG_IMMUTABLE, null, new UserHandle(serviceUserId)));
+ intent.putExtra(Intent.EXTRA_CLIENT_INTENT, clientIntent);
if (!mContext.bindServiceAsUser(intent, newConn,
Context.BIND_AUTO_CREATE | Context.BIND_SHOWING_UI
| Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE
diff --git a/services/core/java/com/android/server/wearable/WearableSensingManagerService.java b/services/core/java/com/android/server/wearable/WearableSensingManagerService.java
index e155a0690128..e14589860a86 100644
--- a/services/core/java/com/android/server/wearable/WearableSensingManagerService.java
+++ b/services/core/java/com/android/server/wearable/WearableSensingManagerService.java
@@ -38,6 +38,7 @@ import android.provider.DeviceConfig;
import android.util.Slog;
import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.infra.AbstractMasterSystemService;
@@ -161,6 +162,32 @@ public class WearableSensingManagerService extends
return null;
}
+ @VisibleForTesting
+ void provideDataStream(@UserIdInt int userId, ParcelFileDescriptor parcelFileDescriptor,
+ RemoteCallback callback) {
+ synchronized (mLock) {
+ final WearableSensingManagerPerUserService mService = getServiceForUserLocked(userId);
+ if (mService != null) {
+ mService.onProvideDataStream(parcelFileDescriptor, callback);
+ } else {
+ Slog.w(TAG, "Service not available.");
+ }
+ }
+ }
+
+ @VisibleForTesting
+ void provideData(@UserIdInt int userId, PersistableBundle data, SharedMemory sharedMemory,
+ RemoteCallback callback) {
+ synchronized (mLock) {
+ final WearableSensingManagerPerUserService mService = getServiceForUserLocked(userId);
+ if (mService != null) {
+ mService.onProvidedData(data, sharedMemory, callback);
+ } else {
+ Slog.w(TAG, "Service not available.");
+ }
+ }
+ }
+
private final class WearableSensingManagerInternal extends IWearableSensingManager.Stub {
final WearableSensingManagerPerUserService mService = getServiceForUserLocked(
UserHandle.getCallingUserId());
@@ -205,6 +232,8 @@ public class WearableSensingManagerService extends
@Override
public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
+ new WearableSensingShellCommand(WearableSensingManagerService.this).exec(
+ this, in, out, err, args, callback, resultReceiver);
}
}
}
diff --git a/services/core/java/com/android/server/wearable/WearableSensingShellCommand.java b/services/core/java/com/android/server/wearable/WearableSensingShellCommand.java
new file mode 100644
index 000000000000..842bccbe0847
--- /dev/null
+++ b/services/core/java/com/android/server/wearable/WearableSensingShellCommand.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wearable;
+
+import android.annotation.NonNull;
+import android.app.wearable.WearableSensingManager;
+import android.content.ComponentName;
+import android.os.Binder;
+import android.os.ParcelFileDescriptor;
+import android.os.PersistableBundle;
+import android.os.RemoteCallback;
+import android.os.ShellCommand;
+import android.util.Slog;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+
+final class WearableSensingShellCommand extends ShellCommand {
+ private static final String TAG = WearableSensingShellCommand.class.getSimpleName();
+
+ static final TestableCallbackInternal sTestableCallbackInternal =
+ new TestableCallbackInternal();
+
+ @NonNull
+ private final WearableSensingManagerService mService;
+
+ private static ParcelFileDescriptor[] sPipe;
+
+ WearableSensingShellCommand(@NonNull WearableSensingManagerService service) {
+ mService = service;
+ }
+
+ /** Callbacks for WearableSensingService results used internally for testing. */
+ static class TestableCallbackInternal {
+ private int mLastStatus;
+
+ public int getLastStatus() {
+ return mLastStatus;
+ }
+
+ @NonNull
+ private RemoteCallback createRemoteStatusCallback() {
+ return new RemoteCallback(result -> {
+ int status = result.getInt(WearableSensingManager.STATUS_RESPONSE_BUNDLE_KEY);
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mLastStatus = status;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ });
+ }
+ }
+
+ @Override
+ public int onCommand(String cmd) {
+ if (cmd == null) {
+ return handleDefaultCommands(cmd);
+ }
+
+ switch (cmd) {
+ case "create-data-stream":
+ return createDataStream();
+ case "destroy-data-stream":
+ return destroyDataStream();
+ case "provide-data-stream":
+ return provideDataStream();
+ case "write-to-data-stream":
+ return writeToDataStream();
+ case "provide-data":
+ return provideData();
+ case "get-last-status-code":
+ return getLastStatusCode();
+ case "get-bound-package":
+ return getBoundPackageName();
+ case "set-temporary-service":
+ return setTemporaryService();
+ default:
+ return handleDefaultCommands(cmd);
+ }
+ }
+
+ @Override
+ public void onHelp() {
+ PrintWriter pw = getOutPrintWriter();
+ pw.println("WearableSensingCommands commands: ");
+ pw.println(" help");
+ pw.println(" Print this help text.");
+ pw.println();
+ pw.println(" create-data-stream: Creates a data stream to be provided.");
+ pw.println(" destroy-data-stream: Destroys a data stream if one was previously created.");
+ pw.println(" provide-data-stream USER_ID: "
+ + "Provides data stream to WearableSensingService.");
+ pw.println(" write-to-data-stream STRING: writes string to data stream.");
+ pw.println(" provide-data USER_ID KEY INTEGER: provide integer as data with key.");
+ pw.println(" get-last-status-code: Prints the latest request status code.");
+ pw.println(" get-bound-package USER_ID:"
+ + " Print the bound package that implements the service.");
+ pw.println(" set-temporary-service USER_ID [PACKAGE_NAME] [COMPONENT_NAME DURATION]");
+ pw.println(" Temporarily (for DURATION ms) changes the service implementation.");
+ pw.println(" To reset, call with just the USER_ID argument.");
+ }
+
+ private int createDataStream() {
+ Slog.d(TAG, "createDataStream");
+ try {
+ sPipe = ParcelFileDescriptor.createPipe();
+ } catch (IOException e) {
+ Slog.d(TAG, "Failed to createDataStream.", e);
+ }
+ return 0;
+ }
+
+ private int destroyDataStream() {
+ Slog.d(TAG, "destroyDataStream");
+ try {
+ if (sPipe != null) {
+ sPipe[0].close();
+ sPipe[1].close();
+ }
+ } catch (IOException e) {
+ Slog.d(TAG, "Failed to destroyDataStream.", e);
+ }
+ return 0;
+ }
+
+ private int provideDataStream() {
+ Slog.d(TAG, "provideDataStream");
+ if (sPipe != null) {
+ final int userId = Integer.parseInt(getNextArgRequired());
+ mService.provideDataStream(userId, sPipe[0],
+ sTestableCallbackInternal.createRemoteStatusCallback());
+ }
+ return 0;
+ }
+
+ private int writeToDataStream() {
+ Slog.d(TAG, "writeToDataStream");
+ if (sPipe != null) {
+ final String value = getNextArgRequired();
+ try {
+ ParcelFileDescriptor writePipe = sPipe[1].dup();
+ OutputStream os = new ParcelFileDescriptor.AutoCloseOutputStream(writePipe);
+ os.write(value.getBytes());
+ } catch (IOException e) {
+ Slog.d(TAG, "Failed to writeToDataStream.", e);
+ }
+ }
+ return 0;
+ }
+
+ private int provideData() {
+ Slog.d(TAG, "provideData");
+ final int userId = Integer.parseInt(getNextArgRequired());
+ final String key = getNextArgRequired();
+ final int value = Integer.parseInt(getNextArgRequired());
+ PersistableBundle data = new PersistableBundle();
+ data.putInt(key, value);
+
+ mService.provideData(userId, data, null,
+ sTestableCallbackInternal.createRemoteStatusCallback());
+ return 0;
+ }
+
+ private int getLastStatusCode() {
+ Slog.d(TAG, "getLastStatusCode");
+ final PrintWriter resultPrinter = getOutPrintWriter();
+ int lastStatus = sTestableCallbackInternal.getLastStatus();
+ resultPrinter.println(lastStatus);
+ return 0;
+ }
+
+ private int setTemporaryService() {
+ final PrintWriter out = getOutPrintWriter();
+ final int userId = Integer.parseInt(getNextArgRequired());
+ final String serviceName = getNextArg();
+ if (serviceName == null) {
+ mService.resetTemporaryService(userId);
+ out.println("WearableSensingManagerService temporary reset. ");
+ return 0;
+ }
+
+ final int duration = Integer.parseInt(getNextArgRequired());
+ mService.setTemporaryService(userId, serviceName, duration);
+ out.println("WearableSensingService temporarily set to " + serviceName
+ + " for " + duration + "ms");
+ return 0;
+ }
+
+ private int getBoundPackageName() {
+ final PrintWriter resultPrinter = getOutPrintWriter();
+ final int userId = Integer.parseInt(getNextArgRequired());
+ final ComponentName componentName = mService.getComponentName(userId);
+ resultPrinter.println(componentName == null ? "" : componentName.getPackageName());
+ return 0;
+ }
+}
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index e1ab291cc00a..13111fb70419 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -740,7 +740,7 @@ class ActivityMetricsLogger {
// visible such as after the top task is finished.
for (int i = mTransitionInfoList.size() - 2; i >= 0; i--) {
final TransitionInfo prevInfo = mTransitionInfoList.get(i);
- if (prevInfo.mIsDrawn || !prevInfo.mLastLaunchedActivity.mVisibleRequested) {
+ if (prevInfo.mIsDrawn || !prevInfo.mLastLaunchedActivity.isVisibleRequested()) {
scheduleCheckActivityToBeDrawn(prevInfo.mLastLaunchedActivity, 0 /* delay */);
}
}
@@ -867,7 +867,7 @@ class ActivityMetricsLogger {
return;
}
if (DEBUG_METRICS) {
- Slog.i(TAG, "notifyVisibilityChanged " + r + " visible=" + r.mVisibleRequested
+ Slog.i(TAG, "notifyVisibilityChanged " + r + " visible=" + r.isVisibleRequested()
+ " state=" + r.getState() + " finishing=" + r.finishing);
}
if (r.isState(ActivityRecord.State.RESUMED) && r.mDisplayContent.isSleeping()) {
@@ -876,7 +876,7 @@ class ActivityMetricsLogger {
// the tracking of launch event.
return;
}
- if (!r.mVisibleRequested || r.finishing) {
+ if (!r.isVisibleRequested() || r.finishing) {
// Check if the tracker can be cancelled because the last launched activity may be
// no longer visible.
scheduleCheckActivityToBeDrawn(r, 0 /* delay */);
@@ -909,7 +909,7 @@ class ActivityMetricsLogger {
// activities in this task may be finished, invisible or drawn, so the transition event
// should be cancelled.
if (t != null && t.forAllActivities(
- a -> a.mVisibleRequested && !a.isReportedDrawn() && !a.finishing)) {
+ a -> a.isVisibleRequested() && !a.isReportedDrawn() && !a.finishing)) {
return;
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index e099aac8c73e..aec06f0a25dd 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -800,7 +800,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// it will sometimes be true a little earlier: when the activity record has
// been shown, but is still waiting for its app transition to execute
// before making its windows shown.
- boolean mVisibleRequested;
+ private boolean mVisibleRequested;
// Last visibility state we reported to the app token.
boolean reportedVisible;
@@ -3622,7 +3622,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// implied that the current finishing activity should be added into stopping list rather
// than destroy immediately.
final boolean isNextNotYetVisible = next != null
- && (!next.nowVisible || !next.mVisibleRequested);
+ && (!next.nowVisible || !next.isVisibleRequested());
// Clear last paused activity to ensure top activity can be resumed during sleeping.
if (isNextNotYetVisible && mDisplayContent.isSleeping()
@@ -4440,7 +4440,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
void transferStartingWindowFromHiddenAboveTokenIfNeeded() {
task.forAllActivities(fromActivity -> {
if (fromActivity == this) return true;
- return !fromActivity.mVisibleRequested && transferStartingWindow(fromActivity);
+ return !fromActivity.isVisibleRequested() && transferStartingWindow(fromActivity);
});
}
@@ -5105,7 +5105,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
* This is the only place that writes {@link #mVisibleRequested} (except unit test). The caller
* outside of this class should use {@link #setVisibility}.
*/
- private void setVisibleRequested(boolean visible) {
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ void setVisibleRequested(boolean visible) {
if (visible == mVisibleRequested) {
return;
}
@@ -5433,25 +5434,20 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
*/
private void postApplyAnimation(boolean visible, boolean fromTransition) {
final boolean usingShellTransitions = mTransitionController.isShellTransitionsEnabled();
- final boolean delayed = isAnimating(PARENTS | CHILDREN,
+ final boolean delayed = !usingShellTransitions && isAnimating(PARENTS | CHILDREN,
ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_WINDOW_ANIMATION
| ANIMATION_TYPE_RECENTS);
- if (!delayed) {
+ if (!delayed && !usingShellTransitions) {
// We aren't delayed anything, but exiting windows rely on the animation finished
// callback being called in case the ActivityRecord was pretending to be delayed,
// which we might have done because we were in closing/opening apps list.
- if (!usingShellTransitions) {
- onAnimationFinished(ANIMATION_TYPE_APP_TRANSITION, null /* AnimationAdapter */);
- if (visible) {
- // The token was made immediately visible, there will be no entrance animation.
- // We need to inform the client the enter animation was finished.
- mEnteringAnimation = true;
- mWmService.mActivityManagerAppTransitionNotifier.onAppTransitionFinishedLocked(
- token);
- }
- } else {
- // update wallpaper target
- setAppLayoutChanges(FINISH_LAYOUT_REDO_WALLPAPER, "ActivityRecord");
+ onAnimationFinished(ANIMATION_TYPE_APP_TRANSITION, null /* AnimationAdapter */);
+ if (visible) {
+ // The token was made immediately visible, there will be no entrance animation.
+ // We need to inform the client the enter animation was finished.
+ mEnteringAnimation = true;
+ mWmService.mActivityManagerAppTransitionNotifier.onAppTransitionFinishedLocked(
+ token);
}
}
@@ -5460,8 +5456,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// updated.
// If we're becoming invisible, update the client visibility if we are not running an
// animation. Otherwise, we'll update client visibility in onAnimationFinished.
- if (visible || !isAnimating(PARENTS, ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS)
- || usingShellTransitions) {
+ if (visible || usingShellTransitions
+ || !isAnimating(PARENTS, ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS)) {
setClientVisible(visible);
}
@@ -6549,7 +6545,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
if (associatedTask == null) {
removeStartingWindow();
} else if (associatedTask.getActivity(
- r -> r.mVisibleRequested && !r.firstWindowDrawn) == null) {
+ r -> r.isVisibleRequested() && !r.firstWindowDrawn) == null) {
// The last drawn activity may not be the one that owns the starting window.
final ActivityRecord r = associatedTask.topActivityContainsStartingWindow();
if (r != null) {
@@ -7442,8 +7438,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
} else if (!show && mLastSurfaceShowing) {
getSyncTransaction().hide(mSurfaceControl);
}
- if (show) {
- mActivityRecordInputSink.applyChangesToSurfaceIfChanged(getSyncTransaction());
+ // Input sink surface is not a part of animation, so just apply in a steady state
+ // (non-sync) with pending transaction.
+ if (show && mSyncState == SYNC_STATE_NONE) {
+ mActivityRecordInputSink.applyChangesToSurfaceIfChanged(getPendingTransaction());
}
}
if (mThumbnail != null) {
diff --git a/services/core/java/com/android/server/wm/ActivityServiceConnectionsHolder.java b/services/core/java/com/android/server/wm/ActivityServiceConnectionsHolder.java
index 30c7b232fcc8..0859d40c0fd1 100644
--- a/services/core/java/com/android/server/wm/ActivityServiceConnectionsHolder.java
+++ b/services/core/java/com/android/server/wm/ActivityServiceConnectionsHolder.java
@@ -92,7 +92,7 @@ public class ActivityServiceConnectionsHolder<T> {
public boolean isActivityVisible() {
synchronized (mService.mGlobalLock) {
- return mActivity.mVisibleRequested || mActivity.isState(RESUMED, PAUSING);
+ return mActivity.isVisibleRequested() || mActivity.isState(RESUMED, PAUSING);
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java
index 56aae2d6db37..05ec3b5d9ed6 100644
--- a/services/core/java/com/android/server/wm/ActivityStartController.java
+++ b/services/core/java/com/android/server/wm/ActivityStartController.java
@@ -561,7 +561,7 @@ public class ActivityStartController {
if (rootTask == null) return false;
final RemoteTransition remote = options.getRemoteTransition();
final ActivityRecord r = rootTask.topRunningActivity();
- if (r == null || r.mVisibleRequested || !r.attachedToProcess() || remote == null
+ if (r == null || r.isVisibleRequested() || !r.attachedToProcess() || remote == null
|| !r.mActivityComponent.equals(intent.getComponent())
// Recents keeps invisible while device is locked.
|| r.mDisplayContent.isKeyguardLocked()) {
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 5c83c4f940d3..1e063754c8c3 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -2637,7 +2637,7 @@ class ActivityStarter {
// If the activity is visible in multi-windowing mode, it may already be on
// the top (visible to user but not the global top), then the result code
// should be START_DELIVERED_TO_TOP instead of START_TASK_TO_FRONT.
- final boolean wasTopOfVisibleRootTask = intentActivity.mVisibleRequested
+ final boolean wasTopOfVisibleRootTask = intentActivity.isVisibleRequested()
&& intentActivity.inMultiWindowMode()
&& intentActivity == mTargetRootTask.topRunningActivity();
// We only want to move to the front, if we aren't going to launch on a
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 7dd8770922e8..eb04687d7d0c 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -4386,6 +4386,13 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
values.touchscreen,
values.uiMode);
+ // Note: certain tests currently run as platform_app which is not allowed
+ // to set debug system properties. To ensure that system properties are set
+ // only when allowed, we check the current UID.
+ if (Process.myUid() == Process.SYSTEM_UID) {
+ SystemProperties.set("debug.tracing.mcc", Integer.toString(values.mcc));
+ SystemProperties.set("debug.tracing.mnc", Integer.toString(values.mnc));
+ }
if (!initLocale && !values.getLocales().isEmpty() && values.userSetLocale) {
final LocaleList locales = values.getLocales();
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 3145ab37ed0d..8a247cf8b4d3 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -145,6 +145,7 @@ import com.android.server.LocalServices;
import com.android.server.am.ActivityManagerService;
import com.android.server.am.HostingRecord;
import com.android.server.am.UserState;
+import com.android.server.pm.PackageManagerServiceUtils;
import com.android.server.utils.Slogf;
import com.android.server.wm.ActivityMetricsLogger.LaunchingState;
@@ -2619,12 +2620,17 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
// ActivityStarter will acquire the lock where the places need, so execute the request
// outside of the lock.
try {
+ // We need to temporarily disable the explicit intent filter matching enforcement
+ // because Task does not store the resolved type of the intent data, causing filter
+ // mismatch in certain cases. (b/240373119)
+ PackageManagerServiceUtils.DISABLE_ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS.set(true);
return mService.getActivityStartController().startActivityInPackage(taskCallingUid,
callingPid, callingUid, callingPackage, callingFeatureId, intent, null, null,
null, 0, 0, options, userId, task, "startActivityFromRecents",
false /* validateIncomingUser */, null /* originatingPendingIntent */,
false /* allowBackgroundActivityStart */);
} finally {
+ PackageManagerServiceUtils.DISABLE_ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS.set(false);
synchronized (mService.mGlobalLock) {
mService.continueWindowLayout();
}
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index fca974325b3b..74d52b2c1458 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -272,6 +272,7 @@ public class AppTransitionController {
handleClosingApps();
handleOpeningApps();
handleChangingApps(transit);
+ handleClosingChangingContainers();
appTransition.setLastAppTransition(transit, topOpeningApp,
topClosingApp, topChangingApp);
@@ -290,6 +291,7 @@ public class AppTransitionController {
mDisplayContent.mClosingApps.clear();
mDisplayContent.mChangingContainers.clear();
mDisplayContent.mUnknownAppVisibilityController.clear();
+ mDisplayContent.mClosingChangingContainers.clear();
// This has changed the visibility of windows, so perform
// a new layout to get them all up-to-date.
@@ -1178,6 +1180,24 @@ public class AppTransitionController {
}
}
+ private void handleClosingChangingContainers() {
+ final ArrayMap<WindowContainer, Rect> containers =
+ mDisplayContent.mClosingChangingContainers;
+ while (!containers.isEmpty()) {
+ final WindowContainer container = containers.keyAt(0);
+ containers.remove(container);
+
+ // For closing changing windows that are part of the transition, they should have been
+ // removed from mClosingChangingContainers in WindowContainer#getAnimationAdapter()
+ // If the closing changing TaskFragment is not part of the transition, update its
+ // surface after removing it from mClosingChangingContainers.
+ final TaskFragment taskFragment = container.asTaskFragment();
+ if (taskFragment != null) {
+ taskFragment.updateOrganizedTaskFragmentSurface();
+ }
+ }
+ }
+
private void handleChangingApps(@TransitionOldType int transit) {
final ArraySet<WindowContainer> apps = mDisplayContent.mChangingContainers;
final int appsCount = apps.size();
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 798e73906761..14131e693561 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -168,6 +168,12 @@ class BackNavigationController {
+ "recents. Overriding back callback to recents controller callback.");
return null;
}
+
+ if (!window.isDrawn()) {
+ ProtoLog.d(WM_DEBUG_BACK_PREVIEW,
+ "Focused window didn't have a valid surface drawn.");
+ return null;
+ }
}
if (window == null) {
@@ -229,6 +235,7 @@ class BackNavigationController {
// We don't have an application callback, let's find the destination of the back gesture
// The search logic should align with ActivityClientController#finishActivity
prevActivity = currentTask.topRunningActivity(currentActivity.token, INVALID_TASK_ID);
+ final boolean isOccluded = isKeyguardOccluded(window);
// TODO Dialog window does not need to attach on activity, check
// window.mAttrs.type != TYPE_BASE_APPLICATION
if ((window.getParent().getChildCount() > 1
@@ -238,16 +245,24 @@ class BackNavigationController {
backType = BackNavigationInfo.TYPE_DIALOG_CLOSE;
removedWindowContainer = window;
} else if (prevActivity != null) {
- // We have another Activity in the same currentTask to go to
- backType = BackNavigationInfo.TYPE_CROSS_ACTIVITY;
- removedWindowContainer = currentActivity;
- prevTask = prevActivity.getTask();
+ if (!isOccluded || prevActivity.canShowWhenLocked()) {
+ // We have another Activity in the same currentTask to go to
+ backType = BackNavigationInfo.TYPE_CROSS_ACTIVITY;
+ removedWindowContainer = currentActivity;
+ prevTask = prevActivity.getTask();
+ } else {
+ backType = BackNavigationInfo.TYPE_CALLBACK;
+ }
} else if (currentTask.returnsToHomeRootTask()) {
- // Our Task should bring back to home
- removedWindowContainer = currentTask;
- prevTask = currentTask.getDisplayArea().getRootHomeTask();
- backType = BackNavigationInfo.TYPE_RETURN_TO_HOME;
- mShowWallpaper = true;
+ if (isOccluded) {
+ backType = BackNavigationInfo.TYPE_CALLBACK;
+ } else {
+ // Our Task should bring back to home
+ removedWindowContainer = currentTask;
+ prevTask = currentTask.getDisplayArea().getRootHomeTask();
+ backType = BackNavigationInfo.TYPE_RETURN_TO_HOME;
+ mShowWallpaper = true;
+ }
} else if (currentActivity.isRootOfTask()) {
// TODO(208789724): Create single source of truth for this, maybe in
// RootWindowContainer
@@ -261,7 +276,9 @@ class BackNavigationController {
backType = BackNavigationInfo.TYPE_CALLBACK;
} else {
prevActivity = prevTask.getTopNonFinishingActivity();
- if (prevTask.isActivityTypeHome()) {
+ if (prevActivity == null || (isOccluded && !prevActivity.canShowWhenLocked())) {
+ backType = BackNavigationInfo.TYPE_CALLBACK;
+ } else if (prevTask.isActivityTypeHome()) {
backType = BackNavigationInfo.TYPE_RETURN_TO_HOME;
mShowWallpaper = true;
} else {
@@ -317,6 +334,12 @@ class BackNavigationController {
return mAnimationTargets.mComposed && mAnimationTargets.mWaitTransition;
}
+ boolean isKeyguardOccluded(WindowState focusWindow) {
+ final KeyguardController kc = mWindowManagerService.mAtmService.mKeyguardController;
+ final int displayId = focusWindow.getDisplayId();
+ return kc.isKeyguardLocked(displayId) && kc.isDisplayOccluded(displayId);
+ }
+
// For legacy transition.
/**
* Once we find the transition targets match back animation targets, remove the target from
@@ -804,7 +827,7 @@ class BackNavigationController {
if (activity == null) {
return;
}
- if (!activity.mVisibleRequested) {
+ if (!activity.isVisibleRequested()) {
activity.setVisibility(true);
}
activity.mLaunchTaskBehind = true;
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 690779d8951e..d5802cf37bad 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -192,6 +192,7 @@ import android.os.Trace;
import android.os.UserHandle;
import android.os.WorkSource;
import android.provider.Settings;
+import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.DisplayMetrics;
import android.util.DisplayUtils;
@@ -356,6 +357,11 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
final ArraySet<ActivityRecord> mClosingApps = new ArraySet<>();
final ArraySet<WindowContainer> mChangingContainers = new ArraySet<>();
final UnknownAppVisibilityController mUnknownAppVisibilityController;
+ /**
+ * If a container is closing when resizing, keeps track of its starting bounds when it is
+ * removed from {@link #mChangingContainers}.
+ */
+ final ArrayMap<WindowContainer, Rect> mClosingChangingContainers = new ArrayMap<>();
private MetricsLogger mMetricsLogger;
@@ -870,11 +876,11 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
final ActivityRecord activity = w.mActivityRecord;
if (gone) Slog.v(TAG, " GONE: mViewVisibility=" + w.mViewVisibility
+ " mRelayoutCalled=" + w.mRelayoutCalled + " visible=" + w.mToken.isVisible()
- + " visibleRequested=" + (activity != null && activity.mVisibleRequested)
+ + " visibleRequested=" + (activity != null && activity.isVisibleRequested())
+ " parentHidden=" + w.isParentWindowHidden());
else Slog.v(TAG, " VIS: mViewVisibility=" + w.mViewVisibility
+ " mRelayoutCalled=" + w.mRelayoutCalled + " visible=" + w.mToken.isVisible()
- + " visibleRequested=" + (activity != null && activity.mVisibleRequested)
+ + " visibleRequested=" + (activity != null && activity.isVisibleRequested())
+ " parentHidden=" + w.isParentWindowHidden());
}
@@ -1699,7 +1705,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
.notifyTaskRequestedOrientationChanged(task.mTaskId, orientation);
}
// The orientation source may not be the top if it uses SCREEN_ORIENTATION_BEHIND.
- final ActivityRecord topCandidate = !r.mVisibleRequested ? topRunningActivity() : r;
+ final ActivityRecord topCandidate = !r.isVisibleRequested() ? topRunningActivity() : r;
if (handleTopActivityLaunchingInDifferentOrientation(
topCandidate, r, true /* checkOpening */)) {
// Display orientation should be deferred until the top fixed rotation is finished.
@@ -2095,13 +2101,13 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
}
/**
- * @see DisplayWindowPolicyController#canShowTasksInRecents()
+ * @see DisplayWindowPolicyController#canShowTasksInHostDeviceRecents()
*/
- boolean canShowTasksInRecents() {
+ boolean canShowTasksInHostDeviceRecents() {
if (mDwpcHelper == null) {
return true;
}
- return mDwpcHelper.canShowTasksInRecents();
+ return mDwpcHelper.canShowTasksInHostDeviceRecents();
}
/**
@@ -2706,7 +2712,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
mWmService.mWindowsChanged = true;
// If the transition finished callback cannot match the token for some reason, make sure the
// rotated state is cleared if it is already invisible.
- if (mFixedRotationLaunchingApp != null && !mFixedRotationLaunchingApp.mVisibleRequested
+ if (mFixedRotationLaunchingApp != null && !mFixedRotationLaunchingApp.isVisibleRequested()
&& !mFixedRotationLaunchingApp.isVisible()
&& !mDisplayRotation.isRotatingSeamlessly()) {
clearFixedRotationLaunchingApp();
@@ -6808,10 +6814,9 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
@Override
public boolean isRequestedVisible(@InsetsType int types) {
- if (types == ime()) {
- return getInsetsStateController().getImeSourceProvider().isImeShowing();
- }
- return (mRequestedVisibleTypes & types) != 0;
+ return ((types & ime()) != 0
+ && getInsetsStateController().getImeSourceProvider().isImeShowing())
+ || (mRequestedVisibleTypes & types) != 0;
}
@Override
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 1fef3c22a523..300deca1d09a 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -57,7 +57,6 @@ import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION;
import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
-import static android.view.WindowManager.TRANSIT_WAKE;
import static android.view.WindowManagerGlobal.ADD_OKAY;
import static android.view.WindowManagerPolicyConstants.ACTION_HDMI_PLUGGED;
import static android.view.WindowManagerPolicyConstants.ALT_BAR_BOTTOM;
@@ -789,13 +788,7 @@ public class DisplayPolicy {
if (!mDisplayContent.isDefaultDisplay) {
return;
}
- if (mAwake && mDisplayContent.mTransitionController.isShellTransitionsEnabled()
- && !mDisplayContent.mTransitionController.isCollecting()) {
- // Start a transition for waking. This is needed for showWhenLocked activities.
- mDisplayContent.mTransitionController.requestTransitionIfNeeded(TRANSIT_WAKE,
- 0 /* flags */, null /* trigger */, mDisplayContent);
- }
- mService.mAtmService.mKeyguardController.updateDeferWakeTransition(
+ mService.mAtmService.mKeyguardController.updateDeferTransitionForAod(
mAwake /* waiting */);
}
}
@@ -1202,7 +1195,6 @@ public class DisplayPolicy {
return null;
}
return (displayFrames, windowContainer, inOutFrame) -> {
- inOutFrame.inset(win.mGivenContentInsets);
final LayoutParams lp = win.mAttrs.forRotation(displayFrames.mRotation);
final InsetsFrameProvider ifp = lp.providedInsets[index];
InsetsFrameProvider.calculateInsetsFrame(displayFrames.mUnrestricted,
@@ -2136,15 +2128,10 @@ public class DisplayPolicy {
}
void updateSystemBarAttributes() {
- WindowState winCandidate = mFocusedWindow;
- if (winCandidate == null && mTopFullscreenOpaqueWindowState != null
- && (mTopFullscreenOpaqueWindowState.mAttrs.flags
- & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) == 0) {
- // Only focusable window can take system bar control.
- winCandidate = mTopFullscreenOpaqueWindowState;
- }
// 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;
}
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index eaa08fd5eb0b..185e06eecabb 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -1537,6 +1537,7 @@ public class DisplayRotation {
private int mHalfFoldSavedRotation = -1; // No saved rotation
private DeviceStateController.FoldState mFoldState =
DeviceStateController.FoldState.UNKNOWN;
+ private boolean mInHalfFoldTransition = false;
boolean overrideFrozenRotation() {
return mFoldState == DeviceStateController.FoldState.HALF_FOLDED;
@@ -1544,6 +1545,7 @@ public class DisplayRotation {
boolean shouldRevertOverriddenRotation() {
return mFoldState == DeviceStateController.FoldState.OPEN // When transitioning to open.
+ && mInHalfFoldTransition
&& mHalfFoldSavedRotation != -1 // Ignore if we've already reverted.
&& mUserRotationMode
== WindowManagerPolicy.USER_ROTATION_LOCKED; // Ignore if we're unlocked.
@@ -1552,6 +1554,7 @@ public class DisplayRotation {
int revertOverriddenRotation() {
int savedRotation = mHalfFoldSavedRotation;
mHalfFoldSavedRotation = -1;
+ mInHalfFoldTransition = false;
return savedRotation;
}
@@ -1577,16 +1580,11 @@ public class DisplayRotation {
mService.updateRotation(false /* alwaysSendConfiguration */,
false /* forceRelayout */);
} else {
- // Revert the rotation to our saved value if we transition from HALF_FOLDED.
- if (mHalfFoldSavedRotation != -1) {
- mRotation = mHalfFoldSavedRotation;
- }
- // Tell the device to update its orientation (mFoldState is still HALF_FOLDED here
- // so we will override USER_ROTATION_LOCKED and allow a rotation).
+ mInHalfFoldTransition = true;
+ mFoldState = newState;
+ // Tell the device to update its orientation.
mService.updateRotation(false /* alwaysSendConfiguration */,
false /* forceRelayout */);
- // Once we are rotated, set mFoldstate, effectively removing the lock override.
- mFoldState = newState;
}
}
}
@@ -1683,6 +1681,7 @@ public class DisplayRotation {
private static class RotationHistory {
private static final int MAX_SIZE = 8;
+ private static final int NO_FOLD_CONTROLLER = -2;
private static class Record {
final @Surface.Rotation int mFromRotation;
final @Surface.Rotation int mToRotation;
@@ -1694,6 +1693,9 @@ public class DisplayRotation {
final String mLastOrientationSource;
final @ActivityInfo.ScreenOrientation int mSourceOrientation;
final long mTimestamp = System.currentTimeMillis();
+ final int mHalfFoldSavedRotation;
+ final boolean mInHalfFoldTransition;
+ final DeviceStateController.FoldState mFoldState;
Record(DisplayRotation dr, int fromRotation, int toRotation) {
mFromRotation = fromRotation;
@@ -1719,6 +1721,15 @@ public class DisplayRotation {
mLastOrientationSource = null;
mSourceOrientation = SCREEN_ORIENTATION_UNSET;
}
+ if (dr.mFoldController != null) {
+ mHalfFoldSavedRotation = dr.mFoldController.mHalfFoldSavedRotation;
+ mInHalfFoldTransition = dr.mFoldController.mInHalfFoldTransition;
+ mFoldState = dr.mFoldController.mFoldState;
+ } else {
+ mHalfFoldSavedRotation = NO_FOLD_CONTROLLER;
+ mInHalfFoldTransition = false;
+ mFoldState = DeviceStateController.FoldState.UNKNOWN;
+ }
}
void dump(String prefix, PrintWriter pw) {
@@ -1735,6 +1746,12 @@ public class DisplayRotation {
if (mNonDefaultRequestingTaskDisplayArea != null) {
pw.println(prefix + " requestingTda=" + mNonDefaultRequestingTaskDisplayArea);
}
+ if (mHalfFoldSavedRotation != NO_FOLD_CONTROLLER) {
+ pw.println(prefix + " halfFoldSavedRotation="
+ + mHalfFoldSavedRotation
+ + " mInHalfFoldTransition=" + mInHalfFoldTransition
+ + " mFoldState=" + mFoldState);
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/DisplayWindowPolicyControllerHelper.java b/services/core/java/com/android/server/wm/DisplayWindowPolicyControllerHelper.java
index 6f821b55e54a..69fd00cc660e 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowPolicyControllerHelper.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowPolicyControllerHelper.java
@@ -153,13 +153,13 @@ class DisplayWindowPolicyControllerHelper {
}
/**
- * @see DisplayWindowPolicyController#canShowTasksInRecents()
+ * @see DisplayWindowPolicyController#canShowTasksInHostDeviceRecents()
*/
- public final boolean canShowTasksInRecents() {
+ public final boolean canShowTasksInHostDeviceRecents() {
if (mDisplayWindowPolicyController == null) {
return true;
}
- return mDisplayWindowPolicyController.canShowTasksInRecents();
+ return mDisplayWindowPolicyController.canShowTasksInHostDeviceRecents();
}
/**
diff --git a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
index 7bb036d0b1e0..bd837940dfb2 100644
--- a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
+++ b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
@@ -191,7 +191,7 @@ class EnsureActivitiesVisibleHelper {
if (!r.attachedToProcess()) {
makeVisibleAndRestartIfNeeded(mStarting, mConfigChanges, isTop,
resumeTopActivity && isTop, r);
- } else if (r.mVisibleRequested) {
+ } else if (r.isVisibleRequested()) {
// If this activity is already visible, then there is nothing to do here.
if (DEBUG_VISIBILITY) {
Slog.v(TAG_VISIBILITY, "Skipping: already visible at " + r);
@@ -244,7 +244,7 @@ class EnsureActivitiesVisibleHelper {
// invisible. If the app is already visible, it must have died while it was visible. In this
// case, we'll show the dead window but will not restart the app. Otherwise we could end up
// thrashing.
- if (!isTop && r.mVisibleRequested && !r.isState(INITIALIZING)) {
+ if (!isTop && r.isVisibleRequested() && !r.isState(INITIALIZING)) {
return;
}
@@ -256,7 +256,7 @@ class EnsureActivitiesVisibleHelper {
if (r != starting) {
r.startFreezingScreenLocked(configChanges);
}
- if (!r.mVisibleRequested || r.mLaunchTaskBehind) {
+ if (!r.isVisibleRequested() || r.mLaunchTaskBehind) {
if (DEBUG_VISIBILITY) {
Slog.v(TAG_VISIBILITY, "Starting and making visible: " + r);
}
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index 1567fa760a94..e9badefe1bb1 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -37,6 +37,7 @@ import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG
import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_TO_SHADE;
import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_WITH_WALLPAPER;
+import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
@@ -176,7 +177,7 @@ class KeyguardController {
final boolean keyguardChanged = (keyguardShowing != state.mKeyguardShowing)
|| (state.mKeyguardGoingAway && keyguardShowing && !aodRemoved);
if (aodRemoved) {
- updateDeferWakeTransition(false /* waiting */);
+ updateDeferTransitionForAod(false /* waiting */);
}
if (!keyguardChanged && !aodChanged) {
setWakeTransitionReady();
@@ -533,24 +534,25 @@ class KeyguardController {
private final Runnable mResetWaitTransition = () -> {
synchronized (mWindowManager.mGlobalLock) {
- updateDeferWakeTransition(false /* waiting */);
+ updateDeferTransitionForAod(false /* waiting */);
}
};
- void updateDeferWakeTransition(boolean waiting) {
+ // Defer transition until AOD dismissed.
+ void updateDeferTransitionForAod(boolean waiting) {
if (waiting == mWaitingForWakeTransition) {
return;
}
- if (!mWindowManager.mAtmService.getTransitionController().isShellTransitionsEnabled()) {
+ if (!mService.getTransitionController().isCollecting()) {
return;
}
- // if aod is showing, defer the wake transition until aod state changed.
+ // if AOD is showing, defer the wake transition until AOD state changed.
if (waiting && isAodShowing(DEFAULT_DISPLAY)) {
mWaitingForWakeTransition = true;
mWindowManager.mAtmService.getTransitionController().deferTransitionReady();
mWindowManager.mH.postDelayed(mResetWaitTransition, DEFER_WAKE_TRANSITION_TIMEOUT_MS);
} else if (!waiting) {
- // dismiss the deferring if the aod state change or cancel awake.
+ // dismiss the deferring if the AOD state change or cancel awake.
mWaitingForWakeTransition = false;
mWindowManager.mAtmService.getTransitionController().continueTransitionReady();
mWindowManager.mH.removeCallbacks(mResetWaitTransition);
@@ -648,6 +650,12 @@ class KeyguardController {
mRequestDismissKeyguard = lastDismissKeyguardActivity != mDismissingKeyguardActivity
&& !mOccluded && !mKeyguardGoingAway
&& mDismissingKeyguardActivity != null;
+ if (mOccluded && mKeyguardShowing && !display.isSleeping() && !top.fillsParent()
+ && display.mWallpaperController.getWallpaperTarget() == null) {
+ // The occluding activity may be translucent or not fill screen. Then let wallpaper
+ // to check whether it should set itself as target to avoid blank background.
+ display.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
+ }
if (mTopTurnScreenOnActivity != lastTurnScreenOnActivity
&& mTopTurnScreenOnActivity != null
@@ -657,10 +665,18 @@ class KeyguardController {
mTopTurnScreenOnActivity.setCurrentLaunchCanTurnScreenOn(false);
}
+ boolean hasChange = false;
if (lastOccluded != mOccluded) {
controller.handleOccludedChanged(mDisplayId, mTopOccludesActivity);
+ hasChange = true;
} else if (!lastKeyguardGoingAway && mKeyguardGoingAway) {
controller.handleKeyguardGoingAwayChanged(display);
+ hasChange = true;
+ }
+ // Collect the participates for shell transition, so that transition won't happen too
+ // early since the transition was set ready.
+ if (hasChange && top != null && (mOccluded || mKeyguardGoingAway)) {
+ display.mTransitionController.collect(top);
}
}
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index bb4c482118a1..bcea6f4db1dc 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -673,6 +673,12 @@ final class LetterboxUiController {
+ getHorizontalPositionMultiplier(mActivityRecord.getParent().getConfiguration()));
pw.println(prefix + " letterboxVerticalPositionMultiplier="
+ getVerticalPositionMultiplier(mActivityRecord.getParent().getConfiguration()));
+ pw.println(prefix + " letterboxPositionForHorizontalReachability="
+ + LetterboxConfiguration.letterboxHorizontalReachabilityPositionToString(
+ mLetterboxConfiguration.getLetterboxPositionForHorizontalReachability()));
+ pw.println(prefix + " letterboxPositionForVerticalReachability="
+ + LetterboxConfiguration.letterboxVerticalReachabilityPositionToString(
+ mLetterboxConfiguration.getLetterboxPositionForVerticalReachability()));
pw.println(prefix + " fixedOrientationLetterboxAspectRatio="
+ mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio());
pw.println(prefix + " defaultMinAspectRatioForUnresizableApps="
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index 1fc061b2ca78..c827062ce70d 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -1393,7 +1393,7 @@ class RecentTasks {
// Ignore the task if it is started on a display which is not allow to show its tasks on
// Recents.
if (task.getDisplayContent() != null
- && !task.getDisplayContent().canShowTasksInRecents()) {
+ && !task.getDisplayContent().canShowTasksInHostDeviceRecents()) {
return false;
}
diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java
index ffe3374e6658..be9058840492 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimation.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimation.java
@@ -112,7 +112,7 @@ class RecentsAnimation implements RecentsAnimationCallbacks, OnRootTaskOrderChan
mTargetActivityType);
ActivityRecord targetActivity = getTargetActivity(targetRootTask);
if (targetActivity != null) {
- if (targetActivity.mVisibleRequested || targetActivity.isTopRunningActivity()) {
+ if (targetActivity.isVisibleRequested() || targetActivity.isTopRunningActivity()) {
// The activity is ready.
return;
}
@@ -195,7 +195,7 @@ class RecentsAnimation implements RecentsAnimationCallbacks, OnRootTaskOrderChan
// Send launch hint if we are actually launching the target. If it's already visible
// (shouldn't happen in general) we don't need to send it.
- if (targetActivity == null || !targetActivity.mVisibleRequested) {
+ if (targetActivity == null || !targetActivity.isVisibleRequested()) {
mService.mRootWindowContainer.startPowerModeLaunchIfNeeded(
true /* forceSend */, targetActivity);
}
diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java
index d34e610fa0fd..3635ebbad018 100644
--- a/services/core/java/com/android/server/wm/RemoteAnimationController.java
+++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java
@@ -104,10 +104,29 @@ class RemoteAnimationController implements DeathRecipient {
RemoteAnimationRecord createRemoteAnimationRecord(WindowContainer windowContainer,
Point position, Rect localBounds, Rect endBounds, Rect startBounds,
boolean showBackdrop) {
+ return createRemoteAnimationRecord(windowContainer, position, localBounds, endBounds,
+ startBounds, showBackdrop, startBounds != null /* shouldCreateSnapshot */);
+ }
+
+ /**
+ * Creates an animation record for each individual {@link WindowContainer}.
+ *
+ * @param windowContainer The windows to animate.
+ * @param position The position app bounds relative to its parent.
+ * @param localBounds The bounds of the app relative to its parent.
+ * @param endBounds The end bounds after the transition, in screen coordinates.
+ * @param startBounds The start bounds before the transition, in screen coordinates.
+ * @param showBackdrop To show background behind a window during animation.
+ * @param shouldCreateSnapshot Whether this target should create a snapshot animation.
+ * @return The record representing animation(s) to run on the app.
+ */
+ RemoteAnimationRecord createRemoteAnimationRecord(WindowContainer windowContainer,
+ Point position, Rect localBounds, Rect endBounds, Rect startBounds,
+ boolean showBackdrop, boolean shouldCreateSnapshot) {
ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "createAnimationAdapter(): container=%s",
windowContainer);
final RemoteAnimationRecord adapters = new RemoteAnimationRecord(windowContainer, position,
- localBounds, endBounds, startBounds, showBackdrop);
+ localBounds, endBounds, startBounds, showBackdrop, shouldCreateSnapshot);
mPendingAnimations.add(adapters);
return adapters;
}
@@ -441,14 +460,15 @@ class RemoteAnimationController implements DeathRecipient {
private @RemoteAnimationTarget.Mode int mMode = RemoteAnimationTarget.MODE_CHANGING;
RemoteAnimationRecord(WindowContainer windowContainer, Point endPos, Rect localBounds,
- Rect endBounds, Rect startBounds, boolean showBackdrop) {
+ Rect endBounds, @Nullable Rect startBounds, boolean showBackdrop,
+ boolean shouldCreateSnapshot) {
mWindowContainer = windowContainer;
mShowBackdrop = showBackdrop;
if (startBounds != null) {
mStartBounds = new Rect(startBounds);
mAdapter = new RemoteAnimationAdapterWrapper(this, endPos, localBounds, endBounds,
mStartBounds, mShowBackdrop);
- if (mRemoteAnimationAdapter.getChangeNeedsSnapshot()) {
+ if (shouldCreateSnapshot && mRemoteAnimationAdapter.getChangeNeedsSnapshot()) {
final Rect thumbnailLocalBounds = new Rect(startBounds);
thumbnailLocalBounds.offsetTo(0, 0);
// Snapshot is located at (0,0) of the animation leash. It doesn't have size
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 64cca87db65a..4c7f6fe7a8b4 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -2099,6 +2099,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
// from the client organizer, so the PIP activity can get the correct config
// from the Task, and prevent conflict with the PipTaskOrganizer.
tf.updateRequestedOverrideConfiguration(EMPTY);
+ tf.updateRelativeEmbeddedBounds();
}
});
rootTask.setWindowingMode(WINDOWING_MODE_PINNED);
@@ -2619,7 +2620,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
final ArrayList<Task> addedTasks = new ArrayList<>();
forAllActivities((r) -> {
final Task task = r.getTask();
- if (r.mVisibleRequested && r.mStartingData == null && !addedTasks.contains(task)) {
+ if (r.isVisibleRequested() && r.mStartingData == null && !addedTasks.contains(task)) {
r.showStartingWindow(true /*taskSwitch*/);
addedTasks.add(task);
}
@@ -2644,7 +2645,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
forAllLeafTasks(task -> {
final int oldRank = task.mLayerRank;
final ActivityRecord r = task.topRunningActivityLocked();
- if (r != null && r.mVisibleRequested) {
+ if (r != null && r.isVisibleRequested()) {
task.mLayerRank = ++mTmpTaskLayerRank;
} else {
task.mLayerRank = Task.LAYER_RANK_INVISIBLE;
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 356cbdacbed6..6d4a526fd954 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -498,6 +498,12 @@ class Task extends TaskFragment {
*/
boolean mInRemoveTask;
+ /**
+ * When set, disassociate the leaf task if relaunched and reparented it to TDA as root task if
+ * possible.
+ */
+ boolean mReparentLeafTaskIfRelaunch;
+
private final AnimatingActivityRegistry mAnimatingActivityRegistry =
new AnimatingActivityRegistry();
@@ -1585,15 +1591,22 @@ class Task extends TaskFragment {
removeChild(r, reason);
});
} else {
+ final ArrayList<ActivityRecord> finishingActivities = new ArrayList<>();
+ forAllActivities(r -> {
+ if (r.finishing || (excludingTaskOverlay && r.isTaskOverlay())) {
+ return;
+ }
+ finishingActivities.add(r);
+ });
+
// Finish or destroy apps from the bottom to ensure that all the other activity have
// been finished and the top task in another task gets resumed when a top activity is
// removed. Otherwise, the next top activity could be started while the top activity
// is removed, which is not necessary since the next top activity is on the same Task
// and should also be removed.
- forAllActivities((r) -> {
- if (r.finishing || (excludingTaskOverlay && r.isTaskOverlay())) {
- return;
- }
+ for (int i = finishingActivities.size() - 1; i >= 0; i--) {
+ final ActivityRecord r = finishingActivities.get(i);
+
// Prevent the transition from being executed too early if the top activity is
// resumed but the mVisibleRequested of any other activity is true, the transition
// should wait until next activity resumed.
@@ -1603,7 +1616,7 @@ class Task extends TaskFragment {
} else {
r.destroyIfPossible(reason);
}
- }, false /* traverseTopToBottom */);
+ }
}
}
@@ -2161,7 +2174,7 @@ class Task extends TaskFragment {
}
private boolean shouldStartChangeTransition(int prevWinMode, @NonNull Rect prevBounds) {
- if (!isLeafTask() || !canStartChangeTransition()) {
+ if (!(isLeafTask() || mCreatedByOrganizer) || !canStartChangeTransition()) {
return false;
}
final int newWinMode = getWindowingMode();
@@ -2449,7 +2462,7 @@ class Task extends TaskFragment {
final String myReason = reason + " adjustFocusToNextFocusableTask";
final ActivityRecord top = focusableTask.topRunningActivity();
- if (focusableTask.isActivityTypeHome() && (top == null || !top.mVisibleRequested)) {
+ if (focusableTask.isActivityTypeHome() && (top == null || !top.isVisibleRequested())) {
// If we will be focusing on the root home task next and its current top activity isn't
// visible, then use the move the root home task to top to make the activity visible.
focusableTask.getDisplayArea().moveHomeActivityToTop(myReason);
@@ -2762,7 +2775,7 @@ class Task extends TaskFragment {
*/
private static void getMaxVisibleBounds(ActivityRecord token, Rect out, boolean[] foundTop) {
// skip hidden (or about to hide) apps
- if (token.mIsExiting || !token.isClientVisible() || !token.mVisibleRequested) {
+ if (token.mIsExiting || !token.isClientVisible() || !token.isVisibleRequested()) {
return;
}
final WindowState win = token.findMainWindow();
@@ -3071,7 +3084,7 @@ class Task extends TaskFragment {
* this activity.
*/
ActivityRecord getTopVisibleActivity() {
- return getActivity((r) -> !r.mIsExiting && r.isClientVisible() && r.mVisibleRequested);
+ return getActivity((r) -> !r.mIsExiting && r.isClientVisible() && r.isVisibleRequested());
}
/**
@@ -5720,7 +5733,7 @@ class Task extends TaskFragment {
forAllActivities(r -> {
if (!r.info.packageName.equals(packageName)) return;
r.forceNewConfig = true;
- if (starting != null && r == starting && r.mVisibleRequested) {
+ if (starting != null && r == starting && r.isVisibleRequested()) {
r.startFreezingScreenLocked(CONFIG_SCREEN_LAYOUT);
}
});
@@ -6064,6 +6077,12 @@ class Task extends TaskFragment {
}
}
+ void setReparentLeafTaskIfRelaunch(boolean reparentLeafTaskIfRelaunch) {
+ if (isOrganized()) {
+ mReparentLeafTaskIfRelaunch = reparentLeafTaskIfRelaunch;
+ }
+ }
+
@Override
public void dumpDebug(ProtoOutputStream proto, long fieldId,
@WindowTraceLogLevel int logLevel) {
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index f3ed937ce924..25b58fa622e7 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -899,15 +899,16 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> {
}
} else if (candidateTask != null) {
final int position = onTop ? POSITION_TOP : POSITION_BOTTOM;
- final Task launchRootTask = getLaunchRootTask(resolvedWindowingMode, activityType,
+ final Task launchParentTask = getLaunchRootTask(resolvedWindowingMode, activityType,
options, sourceTask, launchFlags, candidateTask);
- if (launchRootTask != null) {
+ if (launchParentTask != null) {
if (candidateTask.getParent() == null) {
- launchRootTask.addChild(candidateTask, position);
- } else if (candidateTask.getParent() != launchRootTask) {
- candidateTask.reparent(launchRootTask, position);
+ launchParentTask.addChild(candidateTask, position);
+ } else if (candidateTask.getParent() != launchParentTask) {
+ candidateTask.reparent(launchParentTask, position);
}
- } else if (candidateTask.getDisplayArea() != this) {
+ } else if (candidateTask.getDisplayArea() != this
+ || candidateTask.getRootTask().mReparentLeafTaskIfRelaunch) {
if (candidateTask.getParent() == null) {
addChild(candidateTask, position);
} else {
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 443e3049b2ea..212a4745ff4c 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -302,6 +302,14 @@ class TaskFragment extends WindowContainer<WindowContainer> {
private final IBinder mFragmentToken;
/**
+ * The bounds of the embedded TaskFragment relative to the parent Task.
+ * {@code null} if it is not {@link #mIsEmbedded}
+ * TODO(b/261785978) cleanup with legacy app transition
+ */
+ @Nullable
+ private final Rect mRelativeEmbeddedBounds;
+
+ /**
* Whether to delay the call to {@link #updateOrganizedTaskFragmentSurface()} when there is a
* configuration change.
*/
@@ -346,7 +354,7 @@ class TaskFragment extends WindowContainer<WindowContainer> {
}
void process(ActivityRecord start, boolean preserveWindow) {
- if (start == null || !start.mVisibleRequested) {
+ if (start == null || !start.isVisibleRequested()) {
return;
}
reset(preserveWindow);
@@ -383,6 +391,7 @@ class TaskFragment extends WindowContainer<WindowContainer> {
mRootWindowContainer = mAtmService.mRootWindowContainer;
mCreatedByOrganizer = createdByOrganizer;
mIsEmbedded = isEmbedded;
+ mRelativeEmbeddedBounds = isEmbedded ? new Rect() : null;
mTaskFragmentOrganizerController =
mAtmService.mWindowOrganizerController.mTaskFragmentOrganizerController;
mFragmentToken = fragmentToken;
@@ -1356,7 +1365,7 @@ class TaskFragment extends WindowContainer<WindowContainer> {
if (next.attachedToProcess()) {
if (DEBUG_SWITCH) {
Slog.v(TAG_SWITCH, "Resume running: " + next + " stopped=" + next.stopped
- + " visibleRequested=" + next.mVisibleRequested);
+ + " visibleRequested=" + next.isVisibleRequested());
}
// If the previous activity is translucent, force a visibility update of
@@ -1370,7 +1379,7 @@ class TaskFragment extends WindowContainer<WindowContainer> {
|| mLastPausedActivity != null && !mLastPausedActivity.occludesParent();
// This activity is now becoming visible.
- if (!next.mVisibleRequested || next.stopped || lastActivityTranslucent) {
+ if (!next.isVisibleRequested() || next.stopped || lastActivityTranslucent) {
next.app.addToPendingTop();
next.setVisibility(true);
}
@@ -1421,7 +1430,7 @@ class TaskFragment extends WindowContainer<WindowContainer> {
// Do over!
mTaskSupervisor.scheduleResumeTopActivities();
}
- if (!next.mVisibleRequested || next.stopped) {
+ if (!next.isVisibleRequested() || next.stopped) {
next.setVisibility(true);
}
next.completeResumeLocked();
@@ -1732,7 +1741,7 @@ class TaskFragment extends WindowContainer<WindowContainer> {
} else if (prev.attachedToProcess()) {
ProtoLog.v(WM_DEBUG_STATES, "Enqueue pending stop if needed: %s "
+ "wasStopping=%b visibleRequested=%b", prev, wasStopping,
- prev.mVisibleRequested);
+ prev.isVisibleRequested());
if (prev.deferRelaunchUntilPaused) {
// Complete the deferred relaunch that was waiting for pause to complete.
ProtoLog.v(WM_DEBUG_STATES, "Re-launching after pause: %s", prev);
@@ -1742,7 +1751,7 @@ class TaskFragment extends WindowContainer<WindowContainer> {
// We can't clobber it, because the stop confirmation will not be handled.
// We don't need to schedule another stop, we only need to let it happen.
prev.setState(STOPPING, "completePausedLocked");
- } else if (!prev.mVisibleRequested || shouldSleepOrShutDownActivities()) {
+ } else if (!prev.isVisibleRequested() || shouldSleepOrShutDownActivities()) {
// Clear out any deferred client hide we might currently have.
prev.setDeferHidingClient(false);
// If we were visible then resumeTopActivities will release resources before
@@ -2320,11 +2329,7 @@ class TaskFragment extends WindowContainer<WindowContainer> {
@Override
public void onConfigurationChanged(Configuration newParentConfig) {
super.onConfigurationChanged(newParentConfig);
-
- if (mTaskFragmentOrganizer != null) {
- updateOrganizedTaskFragmentSurface();
- }
-
+ updateOrganizedTaskFragmentSurface();
sendTaskFragmentInfoChanged();
}
@@ -2337,8 +2342,13 @@ class TaskFragment extends WindowContainer<WindowContainer> {
updateOrganizedTaskFragmentSurface();
}
- private void updateOrganizedTaskFragmentSurface() {
- if (mDelayOrganizedTaskFragmentSurfaceUpdate) {
+ /**
+ * TaskFragmentOrganizer doesn't have access to the surface for security reasons, so we need to
+ * update its surface on the server side if it is not collected for Shell or in pending
+ * animation.
+ */
+ void updateOrganizedTaskFragmentSurface() {
+ if (mDelayOrganizedTaskFragmentSurfaceUpdate || mTaskFragmentOrganizer == null) {
return;
}
if (mTransitionController.isShellTransitionsEnabled()
@@ -2370,7 +2380,10 @@ class TaskFragment extends WindowContainer<WindowContainer> {
return;
}
- final Rect bounds = getBounds();
+ // If this TaskFragment is closing while resizing, crop to the starting bounds instead.
+ final Rect bounds = isClosingWhenResizing()
+ ? mDisplayContent.mClosingChangingContainers.get(this)
+ : getBounds();
final int width = bounds.width();
final int height = bounds.height();
if (!forceUpdate && width == mLastSurfaceSize.x && height == mLastSurfaceSize.y) {
@@ -2406,16 +2419,62 @@ class TaskFragment extends WindowContainer<WindowContainer> {
}
}
- /** Whether we should prepare a transition for this {@link TaskFragment} bounds change. */
- boolean shouldStartChangeTransition(Rect startBounds) {
+ /**
+ * Gets the relative bounds of this embedded TaskFragment. This should only be called on
+ * embedded TaskFragment.
+ */
+ @NonNull
+ Rect getRelativeEmbeddedBounds() {
+ if (mRelativeEmbeddedBounds == null) {
+ throw new IllegalStateException("The TaskFragment is not embedded");
+ }
+ return mRelativeEmbeddedBounds;
+ }
+
+ /**
+ * Updates the record of the relative bounds of this embedded TaskFragment. This should only be
+ * called when the embedded TaskFragment's override bounds are changed.
+ * Returns {@code true} if the bounds is changed.
+ */
+ void updateRelativeEmbeddedBounds() {
+ // We only record the override bounds, which means it will not be changed when it is filling
+ // Task, and resize with the parent.
+ getRequestedOverrideBounds(mTmpBounds);
+ getRelativePosition(mTmpPoint);
+ mTmpBounds.offsetTo(mTmpPoint.x, mTmpPoint.y);
+ mRelativeEmbeddedBounds.set(mTmpBounds);
+ }
+
+ /**
+ * Updates the record of relative bounds of this embedded TaskFragment, and checks whether we
+ * should prepare a transition for the bounds change.
+ */
+ boolean shouldStartChangeTransition(@NonNull Rect absStartBounds,
+ @NonNull Rect relStartBounds) {
if (mTaskFragmentOrganizer == null || !canStartChangeTransition()) {
return false;
}
- // Only take snapshot if the bounds are resized.
- final Rect endBounds = getConfiguration().windowConfiguration.getBounds();
- return endBounds.width() != startBounds.width()
- || endBounds.height() != startBounds.height();
+ if (mTransitionController.isShellTransitionsEnabled()) {
+ // For Shell transition, the change will be collected anyway, so only take snapshot when
+ // the bounds are resized.
+ final Rect endBounds = getConfiguration().windowConfiguration.getBounds();
+ return endBounds.width() != absStartBounds.width()
+ || endBounds.height() != absStartBounds.height();
+ } else {
+ // For legacy transition, we need to trigger a change transition as long as the bounds
+ // is changed, even if it is not resized.
+ return !relStartBounds.equals(mRelativeEmbeddedBounds);
+ }
+ }
+
+ /** Records the starting bounds of the closing organized TaskFragment. */
+ void setClosingChangingStartBoundsIfNeeded() {
+ if (isOrganizedTaskFragment() && mDisplayContent != null
+ && mDisplayContent.mChangingContainers.remove(this)) {
+ mDisplayContent.mClosingChangingContainers.put(
+ this, new Rect(mSurfaceFreezer.mFreezeBounds));
+ }
}
@Override
diff --git a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
index da73fad99eb7..ad46770432a1 100644
--- a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
+++ b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
@@ -165,9 +165,11 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier {
}
// If the launch windowing mode is still undefined, inherit from the target task if the
// task is already on the right display area (otherwise, the task may be on a different
- // display area that has incompatible windowing mode).
+ // display area that has incompatible windowing mode or the task organizer request to
+ // disassociate the leaf task if relaunched and reparented it to TDA as root task).
if (launchMode == WINDOWING_MODE_UNDEFINED
- && task != null && task.getTaskDisplayArea() == suggestedDisplayArea) {
+ && task != null && task.getTaskDisplayArea() == suggestedDisplayArea
+ && !task.getRootTask().mReparentLeafTaskIfRelaunch) {
launchMode = task.getWindowingMode();
if (DEBUG) {
appendLog("inherit-from-task="
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index 0a2e877d0f46..274d7ff4671a 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -691,6 +691,8 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub {
if (mainWindow == null || mainWindow.mRemoved) {
removalInfo.playRevealAnimation = false;
} else if (removalInfo.playRevealAnimation && playShiftUpAnimation) {
+ removalInfo.roundedCornerRadius =
+ topActivity.mLetterboxUiController.getRoundedCornersRadius(mainWindow);
removalInfo.windowAnimationLeash = applyStartingWindowAnimation(mainWindow);
removalInfo.mainFrame = mainWindow.getRelativeFrame();
}
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index 29c98b9f8b01..c1b9e662df9d 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -16,29 +16,14 @@
package com.android.server.wm;
-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_TRANSLUCENT_NAVIGATION;
-import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS;
-
-import static com.android.internal.policy.DecorView.NAVIGATION_BAR_COLOR_VIEW_ATTRIBUTES;
-import static com.android.internal.policy.DecorView.STATUS_BAR_COLOR_VIEW_ATTRIBUTES;
-import static com.android.internal.policy.DecorView.getNavigationBarRect;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREENSHOT;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.ActivityManager;
-import android.app.ActivityThread;
-import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.RecordingCanvas;
@@ -59,12 +44,11 @@ import android.view.WindowInsets.Type;
import android.view.WindowInsetsController.Appearance;
import android.view.WindowManager.LayoutParams;
import android.window.ScreenCapture;
+import android.window.SnapshotDrawerUtils;
import android.window.TaskSnapshot;
-import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.ColorUtils;
-import com.android.internal.policy.DecorView;
import com.android.server.policy.WindowManagerPolicy.ScreenOffListener;
import com.android.server.wm.utils.InsetUtils;
@@ -585,7 +569,8 @@ class TaskSnapshotController {
final Rect taskBounds = task.getBounds();
final InsetsState insetsState = mainWindow.getInsetsStateWithVisibilityOverride();
final Rect systemBarInsets = getSystemBarInsets(mainWindow.getFrame(), insetsState);
- final SystemBarBackgroundPainter decorPainter = new SystemBarBackgroundPainter(attrs.flags,
+ final SnapshotDrawerUtils.SystemBarBackgroundPainter decorPainter =
+ new SnapshotDrawerUtils.SystemBarBackgroundPainter(attrs.flags,
attrs.privateFlags, attrs.insetsFlags.appearance, task.getTaskDescription(),
mHighResTaskSnapshotScale, mainWindow.getRequestedVisibleTypes());
final int taskWidth = taskBounds.width();
@@ -599,7 +584,7 @@ class TaskSnapshotController {
final RecordingCanvas c = node.start(width, height);
c.drawColor(color);
decorPainter.setInsets(systemBarInsets);
- decorPainter.drawDecors(c /* statusBarExcludeFrame */);
+ decorPainter.drawDecors(c /* statusBarExcludeFrame */, null /* alreadyDrawnFrame */);
node.end(c);
final Bitmap hwBitmap = ThreadedRenderer.createHardwareBitmap(node, width, height);
if (hwBitmap == null) {
@@ -736,92 +721,4 @@ class TaskSnapshotController {
pw.println(prefix + "mTaskSnapshotEnabled=" + mTaskSnapshotEnabled);
mCache.dump(pw, prefix);
}
-
- /**
- * Helper class to draw the background of the system bars in regions the task snapshot isn't
- * filling the window.
- */
- static class SystemBarBackgroundPainter {
-
- private final Paint mStatusBarPaint = new Paint();
- private final Paint mNavigationBarPaint = new Paint();
- private final int mStatusBarColor;
- private final int mNavigationBarColor;
- private final int mWindowFlags;
- private final int mWindowPrivateFlags;
- private final float mScale;
- private final @Type.InsetsType int mRequestedVisibleTypes;
- private final Rect mSystemBarInsets = new Rect();
-
- SystemBarBackgroundPainter(int windowFlags, int windowPrivateFlags, int appearance,
- ActivityManager.TaskDescription taskDescription, float scale,
- @Type.InsetsType int requestedVisibleTypes) {
- mWindowFlags = windowFlags;
- mWindowPrivateFlags = windowPrivateFlags;
- 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(), appearance,
- APPEARANCE_LIGHT_STATUS_BARS,
- taskDescription.getEnsureStatusBarContrastWhenTransparent());
- mNavigationBarColor = DecorView.calculateBarColor(windowFlags,
- FLAG_TRANSLUCENT_NAVIGATION, semiTransparent,
- taskDescription.getNavigationBarColor(), appearance,
- APPEARANCE_LIGHT_NAVIGATION_BARS,
- taskDescription.getEnsureNavigationBarContrastWhenTransparent()
- && context.getResources().getBoolean(R.bool.config_navBarNeedsScrim));
- mStatusBarPaint.setColor(mStatusBarColor);
- mNavigationBarPaint.setColor(mNavigationBarColor);
- mRequestedVisibleTypes = requestedVisibleTypes;
- }
-
- void setInsets(Rect systemBarInsets) {
- mSystemBarInsets.set(systemBarInsets);
- }
-
- int getStatusBarColorViewHeight() {
- final boolean forceBarBackground =
- (mWindowPrivateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS) != 0;
- if (STATUS_BAR_COLOR_VIEW_ATTRIBUTES.isVisible(
- mRequestedVisibleTypes, mStatusBarColor, mWindowFlags, forceBarBackground)) {
- return (int) (mSystemBarInsets.top * mScale);
- } else {
- return 0;
- }
- }
-
- private boolean isNavigationBarColorViewVisible() {
- final boolean forceBarBackground =
- (mWindowPrivateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS) != 0;
- return NAVIGATION_BAR_COLOR_VIEW_ATTRIBUTES.isVisible(
- mRequestedVisibleTypes, mNavigationBarColor, mWindowFlags, forceBarBackground);
- }
-
- void drawDecors(Canvas c) {
- drawStatusBarBackground(c, getStatusBarColorViewHeight());
- drawNavigationBarBackground(c);
- }
-
- @VisibleForTesting
- void drawStatusBarBackground(Canvas c,
- int statusBarHeight) {
- if (statusBarHeight > 0 && Color.alpha(mStatusBarColor) != 0) {
- final int rightInset = (int) (mSystemBarInsets.right * mScale);
- c.drawRect(0, 0, c.getWidth() - rightInset, statusBarHeight, mStatusBarPaint);
- }
- }
-
- @VisibleForTesting
- void drawNavigationBarBackground(Canvas c) {
- final Rect navigationBarRect = new Rect();
- getNavigationBarRect(c.getWidth(), c.getHeight(), mSystemBarInsets, navigationBarRect,
- mScale);
- final boolean visible = isNavigationBarColorViewVisible();
- if (visible && Color.alpha(mNavigationBarColor) != 0 && !navigationBarRect.isEmpty()) {
- c.drawRect(navigationBarRect, mNavigationBarPaint);
- }
- }
- }
}
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 2b11d54898e2..27374561fa12 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -639,8 +639,8 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
if (target.asDisplayContent() != null || target.asActivityRecord() != null) {
t.setCrop(targetLeash, null /* crop */);
} else {
- // Crop to the requested bounds.
- final Rect clipRect = target.getRequestedOverrideBounds();
+ // Crop to the resolved override bounds.
+ final Rect clipRect = target.getResolvedOverrideBounds();
t.setWindowCrop(targetLeash, clipRect.width(), clipRect.height());
}
t.setCornerRadius(targetLeash, 0);
@@ -992,7 +992,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
// show here in the same way that we manually hide in finishTransaction.
for (int i = mParticipants.size() - 1; i >= 0; --i) {
final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord();
- if (ar == null || !ar.mVisibleRequested) continue;
+ if (ar == null || !ar.isVisibleRequested()) continue;
transaction.show(ar.getSurfaceControl());
// Also manually show any non-reported parents. This is necessary in a few cases
@@ -1246,7 +1246,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
ArrayMap<WindowContainer, Integer> reasons = new ArrayMap<>();
for (int i = mParticipants.size() - 1; i >= 0; --i) {
ActivityRecord r = mParticipants.valueAt(i).asActivityRecord();
- if (r == null || !r.mVisibleRequested) continue;
+ if (r == null || !r.isVisibleRequested()) continue;
int transitionReason = APP_TRANSITION_WINDOWS_DRAWN;
// At this point, r is "ready", but if it's not "ALL ready" then it is probably only
// ready due to starting-window.
@@ -1617,8 +1617,11 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
WindowContainer<?> ancestor = findCommonAncestor(sortedTargets, changes, topApp);
- // make leash based on highest (z-order) direct child of ancestor with a participant.
- WindowContainer leashReference = sortedTargets.get(0);
+ // Make leash based on highest (z-order) direct child of ancestor with a participant.
+ // TODO(b/261418859): Handle the case when the target contains window containers which
+ // belong to a different display. As a workaround we use topApp, from which wallpaper
+ // window container is removed, instead of sortedTargets here.
+ WindowContainer leashReference = topApp;
while (leashReference.getParent() != ancestor) {
leashReference = leashReference.getParent();
}
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 99527b1454c1..cf541fcde459 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -38,6 +38,7 @@ import android.os.SystemProperties;
import android.util.ArrayMap;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
+import android.view.SurfaceControl;
import android.view.WindowManager;
import android.window.ITransitionMetricsReporter;
import android.window.ITransitionPlayer;
@@ -116,6 +117,8 @@ class TransitionController {
*/
boolean mBuildingFinishLayers = false;
+ private final SurfaceControl.Transaction mWakeT = new SurfaceControl.Transaction();
+
TransitionController(ActivityTaskManagerService atm,
TaskSnapshotController taskSnapshotController,
TransitionTracer transitionTracer) {
@@ -619,8 +622,16 @@ class TransitionController {
private void updateRunningRemoteAnimation(Transition transition, boolean isPlaying) {
if (mTransitionPlayerProc == null) return;
if (isPlaying) {
+ mWakeT.setEarlyWakeupStart();
+ mWakeT.apply();
+ // Usually transitions put quite a load onto the system already (with all the things
+ // happening in app), so pause task snapshot persisting to not increase the load.
+ mAtm.mWindowManager.mTaskSnapshotController.setPersisterPaused(true);
mTransitionPlayerProc.setRunningRemoteAnimation(true);
} else if (mPlayingTransitions.isEmpty()) {
+ mWakeT.setEarlyWakeupEnd();
+ mWakeT.apply();
+ mAtm.mWindowManager.mTaskSnapshotController.setPersisterPaused(false);
mTransitionPlayerProc.setRunningRemoteAnimation(false);
mRemotePlayer.clear();
return;
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 3b30dd136c73..5de143d91539 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -725,9 +725,9 @@ class WallpaperController {
}
final boolean newTargetHidden = wallpaperTarget.mActivityRecord != null
- && !wallpaperTarget.mActivityRecord.mVisibleRequested;
+ && !wallpaperTarget.mActivityRecord.isVisibleRequested();
final boolean oldTargetHidden = prevWallpaperTarget.mActivityRecord != null
- && !prevWallpaperTarget.mActivityRecord.mVisibleRequested;
+ && !prevWallpaperTarget.mActivityRecord.isVisibleRequested();
ProtoLog.v(WM_DEBUG_WALLPAPER, "Animating wallpapers: "
+ "old: %s hidden=%b new: %s hidden=%b",
diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
index 6ee30bb956f0..32cfb70fd2a4 100644
--- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java
+++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
@@ -136,7 +136,7 @@ class WallpaperWindowToken extends WindowToken {
recentsAnimationController.linkFixedRotationTransformIfNeeded(this);
} else if ((wallpaperTarget.mActivityRecord == null
// Ignore invisible activity because it may be moving to background.
- || wallpaperTarget.mActivityRecord.mVisibleRequested)
+ || wallpaperTarget.mActivityRecord.isVisibleRequested())
&& wallpaperTarget.mToken.hasFixedRotationTransform()) {
// If the wallpaper target has a fixed rotation, we want the wallpaper to follow its
// rotation
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 05dea0eeccec..df8886d76c48 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -813,6 +813,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
void removeImmediately() {
final DisplayContent dc = getDisplayContent();
if (dc != null) {
+ dc.mClosingChangingContainers.remove(this);
mSurfaceFreezer.unfreeze(getSyncTransaction());
}
while (!mChildren.isEmpty()) {
@@ -1022,9 +1023,12 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
* @param dc The display this container is on after changes.
*/
void onDisplayChanged(DisplayContent dc) {
- if (mDisplayContent != null && mDisplayContent.mChangingContainers.remove(this)) {
- // Cancel any change transition queued-up for this container on the old display.
- mSurfaceFreezer.unfreeze(getSyncTransaction());
+ if (mDisplayContent != null) {
+ mDisplayContent.mClosingChangingContainers.remove(this);
+ if (mDisplayContent.mChangingContainers.remove(this)) {
+ // Cancel any change transition queued-up for this container on the old display.
+ mSurfaceFreezer.unfreeze(getSyncTransaction());
+ }
}
mDisplayContent = dc;
if (dc != null && dc != this) {
@@ -1301,6 +1305,13 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
// If we are losing visibility, then a snapshot isn't necessary and we are no-longer
// part of a change transition.
if (!visible) {
+ if (asTaskFragment() != null) {
+ // If the organized TaskFragment is closing while resizing, we want to keep track of
+ // its starting bounds to make sure the animation starts at the correct position.
+ // This should be called before unfreeze() because we record the starting bounds
+ // in SurfaceFreezer.
+ asTaskFragment().setClosingChangingStartBoundsIfNeeded();
+ }
mSurfaceFreezer.unfreeze(getSyncTransaction());
}
WindowContainer parent = getParent();
@@ -1309,6 +1320,12 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
}
}
+ /** Whether this window is closing while resizing. */
+ boolean isClosingWhenResizing() {
+ return mDisplayContent != null
+ && mDisplayContent.mClosingChangingContainers.containsKey(this);
+ }
+
void writeIdentifierToProto(ProtoOutputStream proto, long fieldId) {
final long token = proto.start(fieldId);
proto.write(HASH_CODE, System.identityHashCode(this));
@@ -3008,20 +3025,35 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
// When there are more than one changing containers, it may leave part of the
// screen empty. Show background color to cover that.
showBackdrop = getDisplayContent().mChangingContainers.size() > 1;
+ backdropColor = appTransition.getNextAppTransitionBackgroundColor();
} else {
// Check whether the app has requested to show backdrop for open/close
// transition.
final Animation a = appTransition.getNextAppRequestedAnimation(enter);
- showBackdrop = a != null && a.getShowBackdrop();
+ if (a != null) {
+ showBackdrop = a.getShowBackdrop();
+ backdropColor = a.getBackdropColor();
+ }
}
- backdropColor = appTransition.getNextAppTransitionBackgroundColor();
}
final Rect localBounds = new Rect(mTmpRect);
localBounds.offsetTo(mTmpPoint.x, mTmpPoint.y);
- final RemoteAnimationController.RemoteAnimationRecord adapters =
- controller.createRemoteAnimationRecord(
- this, mTmpPoint, localBounds, screenBounds,
- (isChanging ? mSurfaceFreezer.mFreezeBounds : null), showBackdrop);
+ final RemoteAnimationController.RemoteAnimationRecord adapters;
+ if (!isChanging && !enter && isClosingWhenResizing()) {
+ // Container that is closing while resizing. Pass in the closing start bounds, so
+ // the animation can start with the correct bounds, there won't be a snapshot.
+ // Cleanup the mClosingChangingContainers so that when the animation is finished, it
+ // will reset the surface.
+ final Rect closingStartBounds = getDisplayContent().mClosingChangingContainers
+ .remove(this);
+ adapters = controller.createRemoteAnimationRecord(
+ this, mTmpPoint, localBounds, screenBounds, closingStartBounds,
+ showBackdrop, false /* shouldCreateSnapshot */);
+ } else {
+ final Rect startBounds = isChanging ? mSurfaceFreezer.mFreezeBounds : null;
+ adapters = controller.createRemoteAnimationRecord(
+ this, mTmpPoint, localBounds, screenBounds, startBounds, showBackdrop);
+ }
if (backdropColor != 0) {
adapters.setBackDropColor(backdropColor);
}
@@ -3470,7 +3502,13 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
return;
}
- getRelativePosition(mTmpPos);
+ if (isClosingWhenResizing()) {
+ // This container is closing while resizing, keep its surface at the starting position
+ // to prevent animation flicker.
+ getRelativePosition(mDisplayContent.mClosingChangingContainers.get(this), mTmpPos);
+ } else {
+ getRelativePosition(mTmpPos);
+ }
final int deltaRotation = getRelativeDisplayRotation();
if (mTmpPos.equals(mLastSurfacePosition) && deltaRotation == mLastDeltaRotation) {
return;
@@ -3535,9 +3573,14 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
outSurfaceInsets.setEmpty();
}
+ /** Gets the position of this container in its parent's coordinate. */
void getRelativePosition(Point outPos) {
- final Rect dispBounds = getBounds();
- outPos.set(dispBounds.left, dispBounds.top);
+ getRelativePosition(getBounds(), outPos);
+ }
+
+ /** Gets the position of {@code curBounds} in this container's parent's coordinate. */
+ void getRelativePosition(Rect curBounds, Point outPos) {
+ outPos.set(curBounds.left, curBounds.top);
final WindowContainer parent = getParent();
if (parent != null) {
final Rect parentBounds = parent.getBounds();
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index 283830430c1a..46a30fb725de 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -1221,6 +1221,12 @@ public class WindowManagerShellCommand extends ShellCommand {
pw.println("Default position for vertical reachability: "
+ LetterboxConfiguration.letterboxVerticalReachabilityPositionToString(
mLetterboxConfiguration.getDefaultPositionForVerticalReachability()));
+ pw.println("Current position for horizontal reachability:"
+ + LetterboxConfiguration.letterboxHorizontalReachabilityPositionToString(
+ mLetterboxConfiguration.getLetterboxPositionForHorizontalReachability()));
+ pw.println("Current position for vertical reachability:"
+ + LetterboxConfiguration.letterboxVerticalReachabilityPositionToString(
+ mLetterboxConfiguration.getLetterboxPositionForVerticalReachability()));
pw.println("Is education enabled: "
+ mLetterboxConfiguration.getIsEducationEnabled());
pw.println("Is using split screen aspect ratio as aspect ratio for unresizable apps: "
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index aa1cf563da0b..72411727361a 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -43,6 +43,7 @@ import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_COMPANION_TASK_FRAGMENT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_REPARENT_LEAF_TASK_IF_RELAUNCH;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_START_SHORTCUT;
@@ -147,7 +148,8 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
@VisibleForTesting
final ArrayMap<IBinder, TaskFragment> mLaunchTaskFragments = new ArrayMap<>();
- private final Rect mTmpBounds = new Rect();
+ private final Rect mTmpBounds0 = new Rect();
+ private final Rect mTmpBounds1 = new Rect();
WindowOrganizerController(ActivityTaskManagerService atm) {
mService = atm;
@@ -796,14 +798,15 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
// When the TaskFragment is resized, we may want to create a change transition for it, for
// which we want to defer the surface update until we determine whether or not to start
// change transition.
- mTmpBounds.set(taskFragment.getBounds());
+ mTmpBounds0.set(taskFragment.getBounds());
+ mTmpBounds1.set(taskFragment.getRelativeEmbeddedBounds());
taskFragment.deferOrganizedTaskFragmentSurfaceUpdate();
final int effects = applyChanges(taskFragment, c, errorCallbackToken);
- if (taskFragment.shouldStartChangeTransition(mTmpBounds)) {
- taskFragment.initializeChangeTransition(mTmpBounds);
+ taskFragment.updateRelativeEmbeddedBounds();
+ if (taskFragment.shouldStartChangeTransition(mTmpBounds0, mTmpBounds1)) {
+ taskFragment.initializeChangeTransition(mTmpBounds0);
}
taskFragment.continueOrganizedTaskFragmentSurfaceUpdate();
- mTmpBounds.set(0, 0, 0, 0);
return effects;
}
@@ -1233,7 +1236,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
WindowContainer.fromBinder(hop.getContainer())
.removeLocalInsetsSourceProvider(hop.getInsetsTypes());
break;
- case HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP:
+ case HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP: {
final WindowContainer container = WindowContainer.fromBinder(hop.getContainer());
if (container == null || container.asDisplayArea() == null
|| !container.isAttached()) {
@@ -1244,7 +1247,26 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
container.setAlwaysOnTop(hop.isAlwaysOnTop());
effects |= TRANSACT_EFFECTS_LIFECYCLE;
break;
-
+ }
+ case HIERARCHY_OP_TYPE_SET_REPARENT_LEAF_TASK_IF_RELAUNCH: {
+ final WindowContainer container = WindowContainer.fromBinder(hop.getContainer());
+ final Task task = container != null ? container.asTask() : null;
+ if (task == null || !task.isAttached()) {
+ Slog.e(TAG, "Attempt to operate on unknown or detached container: "
+ + container);
+ break;
+ }
+ if (!task.mCreatedByOrganizer) {
+ throw new UnsupportedOperationException(
+ "Cannot set reparent leaf task flag on non-organized task : " + task);
+ }
+ if (!task.isRootTask()) {
+ throw new UnsupportedOperationException(
+ "Cannot set reparent leaf task flag on non-root task : " + task);
+ }
+ task.setReparentLeafTaskIfRelaunch(hop.isReparentLeafTaskIfRelaunch());
+ break;
+ }
}
return effects;
}
@@ -1887,7 +1909,18 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
// actions.
taskFragment.setTaskFragmentOrganizer(creationParams.getOrganizer(),
ownerActivity.getUid(), ownerActivity.info.processName);
- ownerTask.addChild(taskFragment, POSITION_TOP);
+ final int position;
+ if (creationParams.getPairedPrimaryFragmentToken() != null) {
+ // When there is a paired primary TaskFragment, we want to place the new TaskFragment
+ // right above the paired one to make sure there is no other window in between.
+ final TaskFragment pairedPrimaryTaskFragment = getTaskFragment(
+ creationParams.getPairedPrimaryFragmentToken());
+ final int pairedPosition = ownerTask.mChildren.indexOf(pairedPrimaryTaskFragment);
+ position = pairedPosition != -1 ? pairedPosition + 1 : POSITION_TOP;
+ } else {
+ position = POSITION_TOP;
+ }
+ ownerTask.addChild(taskFragment, position);
taskFragment.setWindowingMode(creationParams.getWindowingMode());
taskFragment.setBounds(creationParams.getInitialBounds());
mLaunchTaskFragments.put(creationParams.getFragmentToken(), taskFragment);
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 8f63e9317962..d2cd8f8e8b0b 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -765,7 +765,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
// - no longer visible OR
// - not focusable (in PiP mode for instance)
if (topDisplay == null
- || !mPreQTopResumedActivity.mVisibleRequested
+ || !mPreQTopResumedActivity.isVisibleRequested()
|| !mPreQTopResumedActivity.isFocusable()) {
canUpdate = true;
}
@@ -874,7 +874,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
// to those activities that are part of the package whose app-specific settings changed
if (packageName.equals(r.packageName)
&& r.applyAppSpecificConfig(nightMode, localesOverride)
- && r.mVisibleRequested) {
+ && r.isVisibleRequested()) {
r.ensureActivityConfiguration(0 /* globalChanges */, true /* preserveWindow */);
}
}
@@ -956,7 +956,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
}
// Don't consider any activities that are currently not in a state where they
// can be destroyed.
- if (r.mVisibleRequested || !r.stopped || !r.hasSavedState() || !r.isDestroyable()
+ if (r.isVisibleRequested() || !r.stopped || !r.hasSavedState() || !r.isDestroyable()
|| r.isState(STARTED, RESUMED, PAUSING, PAUSED, STOPPING)) {
if (DEBUG_RELEASE) Slog.d(TAG_RELEASE, "Not releasing in-use activity: " + r);
continue;
@@ -1002,7 +1002,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
final int displayId = r.getDisplayId();
final Context c = root.getDisplayUiContext(displayId);
- if (c != null && r.mVisibleRequested && !displayContexts.contains(c)) {
+ if (c != null && r.isVisibleRequested() && !displayContexts.contains(c)) {
displayContexts.add(c);
}
}
@@ -1070,7 +1070,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
if (task != null && task.mLayerRank != Task.LAYER_RANK_INVISIBLE) {
stateFlags |= ACTIVITY_STATE_FLAG_HAS_ACTIVITY_IN_VISIBLE_TASK;
}
- if (r.mVisibleRequested) {
+ if (r.isVisibleRequested()) {
if (r.isState(RESUMED)) {
stateFlags |= ACTIVITY_STATE_FLAG_HAS_RESUMED;
}
@@ -1282,7 +1282,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
}
for (int i = activities.size() - 1; i >= 0; i--) {
final ActivityRecord r = activities.get(i);
- if (r.mVisibleRequested || r.isVisible()) {
+ if (r.isVisibleRequested() || r.isVisible()) {
// While an activity launches a new activity, it's possible that the old activity
// is already requested to be hidden (mVisibleRequested=false), but this visibility
// is not yet committed, so isVisible()=true.
@@ -1503,7 +1503,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
Configuration overrideConfig = new Configuration(r.getRequestedOverrideConfiguration());
overrideConfig.assetsSeq = assetSeq;
r.onRequestedOverrideConfigurationChanged(overrideConfig);
- if (r.mVisibleRequested) {
+ if (r.isVisibleRequested()) {
r.ensureActivityConfiguration(0, true);
}
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 73759d3a3362..1b7bd9e1f36f 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1957,7 +1957,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
*/
// TODO: Can we consolidate this with #isVisible() or have a more appropriate name for this?
boolean isWinVisibleLw() {
- return (mActivityRecord == null || mActivityRecord.mVisibleRequested
+ return (mActivityRecord == null || mActivityRecord.isVisibleRequested()
|| mActivityRecord.isAnimating(TRANSITION | PARENTS)) && isVisible();
}
@@ -1994,7 +1994,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
final ActivityRecord atoken = mActivityRecord;
return (mHasSurface || (!mRelayoutCalled && mViewVisibility == View.VISIBLE))
&& isVisibleByPolicy() && !isParentWindowHidden()
- && (atoken == null || atoken.mVisibleRequested)
+ && (atoken == null || atoken.isVisibleRequested())
&& !mAnimatingExit && !mDestroying;
}
@@ -2090,7 +2090,10 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
} else {
final Task task = getTask();
final boolean canFromTask = task != null && task.canAffectSystemUiFlags();
- return canFromTask && mActivityRecord.isVisible();
+ return canFromTask && mActivityRecord.isVisible()
+ // Do not let snapshot window control the bar
+ && (mAttrs.type != TYPE_APPLICATION_STARTING
+ || !(mStartingData instanceof SnapshotStartingData));
}
}
@@ -2101,7 +2104,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
boolean isDisplayed() {
final ActivityRecord atoken = mActivityRecord;
return isDrawn() && isVisibleByPolicy()
- && ((!isParentWindowHidden() && (atoken == null || atoken.mVisibleRequested))
+ && ((!isParentWindowHidden() && (atoken == null || atoken.isVisibleRequested()))
|| isAnimating(TRANSITION | PARENTS));
}
@@ -2123,7 +2126,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
// a layout since they can request relayout when client visibility is false.
// TODO (b/157682066) investigate if we can clean up isVisible
|| (atoken == null && !(wouldBeVisibleIfPolicyIgnored() && isVisibleByPolicy()))
- || (atoken != null && !atoken.mVisibleRequested)
+ || (atoken != null && !atoken.isVisibleRequested())
|| isParentWindowGoneForLayout()
|| (mAnimatingExit && !isAnimatingLw())
|| mDestroying;
@@ -2170,7 +2173,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
return;
}
if (mActivityRecord != null) {
- if (!mActivityRecord.mVisibleRequested) return;
+ if (!mActivityRecord.isVisibleRequested()) return;
if (mActivityRecord.allDrawn) {
// The allDrawn of activity is reset when the visibility is changed to visible, so
// the content should be ready if allDrawn is set.
@@ -2743,7 +2746,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
+ " exiting=" + mAnimatingExit + " destroying=" + mDestroying);
if (mActivityRecord != null) {
Slog.i(TAG_WM, " mActivityRecord.visibleRequested="
- + mActivityRecord.mVisibleRequested);
+ + mActivityRecord.isVisibleRequested());
}
}
}
@@ -3219,7 +3222,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
}
return !mActivityRecord.getTask().getRootTask().shouldIgnoreInput()
- && mActivityRecord.mVisibleRequested;
+ && mActivityRecord.isVisibleRequested();
}
/**
@@ -3875,7 +3878,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
// the client erroneously accepting a configuration that would have otherwise caused an
// activity restart. We instead hand back the last reported {@link MergedConfiguration}.
if (useLatestConfig || (relayoutVisible && (mActivityRecord == null
- || mActivityRecord.mVisibleRequested))) {
+ || mActivityRecord.isVisibleRequested()))) {
final Configuration globalConfig = getProcessGlobalConfiguration();
final Configuration overrideConfig = getMergedOverrideConfiguration();
outMergedConfiguration.setConfiguration(globalConfig, overrideConfig);
@@ -4734,7 +4737,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
+ " during animation: policyVis=" + isVisibleByPolicy()
+ " parentHidden=" + isParentWindowHidden()
+ " tok.visibleRequested="
- + (mActivityRecord != null && mActivityRecord.mVisibleRequested)
+ + (mActivityRecord != null && mActivityRecord.isVisibleRequested())
+ " tok.visible=" + (mActivityRecord != null && mActivityRecord.isVisible())
+ " animating=" + isAnimating(TRANSITION | PARENTS)
+ " tok animating="
@@ -5195,7 +5198,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
+ " pv=" + isVisibleByPolicy()
+ " mDrawState=" + mWinAnimator.mDrawState
+ " ph=" + isParentWindowHidden()
- + " th=" + (mActivityRecord != null && mActivityRecord.mVisibleRequested)
+ + " th=" + (mActivityRecord != null && mActivityRecord.isVisibleRequested())
+ " a=" + isAnimating(TRANSITION | PARENTS));
}
}
diff --git a/services/core/java/com/android/server/wm/utils/StateMachine.java b/services/core/java/com/android/server/wm/utils/StateMachine.java
new file mode 100644
index 000000000000..91a5dc43a69d
--- /dev/null
+++ b/services/core/java/com/android/server/wm/utils/StateMachine.java
@@ -0,0 +1,280 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.utils;
+
+import android.annotation.IntRange;
+import android.annotation.Nullable;
+import android.util.IntArray;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.util.AnnotationValidations;
+
+import java.util.ArrayDeque;
+import java.util.Queue;
+
+/**
+ * Simple hierarchical state machine.
+ *
+ * The state is represented by an integer value. The root state has a value {@code 0x0}, and top
+ * level state has a value in range {@code 0x1} to {@code 0xF}. To indicate a state B is a sub state
+ * of a state A, assign an integer state_value(B) = state_value(A) << 4 + (0x0 .. 0xF).
+ */
+public class StateMachine {
+ private static final String TAG = "StateMachine";
+
+ /**
+ * Interface for implementing state specific actions.
+ */
+ public interface Handler {
+ /**
+ * Called when state machine changes its state to this state.
+ */
+ default void enter() {}
+
+ /**
+ * Called when state machine changes its state from this state to other state.
+ */
+ default void exit() {}
+
+ /**
+ * @param event type of this event.
+ * @param param parameter passed to {@link StateMachine#handle(int, Object)}
+ * @return {@code true} if the event was handled in this handler, so we don't need to
+ * check the parent state. Otherwise, handle() of the parent state is triggered.
+ */
+ default boolean handle(int event, @Nullable Object param) {
+ return false;
+ }
+ }
+
+ /**
+ * The most recent state requested by transit() call.
+ *
+ * @note When transit() is called recursively, this might not be same value as mState until
+ * transit() finishes.
+ */
+ private int mLastRequestedState;
+
+ /**
+ * The current state of this state machine.
+ */
+ private int mState;
+
+ private final IntArray mTmp = new IntArray();
+ private final SparseArray<Handler> mStateHandlers = new SparseArray<>();
+
+ /**
+ * Actions which need to execute to finish requested transition.
+ */
+ private final Queue<Command> mCommands = new ArrayDeque<>();
+
+ protected static class Command {
+ static final int COMMIT = 1;
+ static final int ENTER = 2;
+ static final int EXIT = 3;
+
+ final int mType;
+ final int mState;
+
+ private Command(int type, @IntRange(from = 0) int state) {
+ mType = type;
+ AnnotationValidations.validate(IntRange.class, null, state, "from", 0);
+ mState = state;
+ }
+
+ static Command newCommit(int state) {
+ return new Command(COMMIT, state);
+ }
+
+ static Command newEnter(int state) {
+ return new Command(ENTER, state);
+ }
+
+ static Command newExit(int state) {
+ return new Command(EXIT, state);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("Command{ type: ");
+ switch (mType) {
+ case COMMIT:
+ sb.append("commit");
+ break;
+ case ENTER:
+ sb.append("enter");
+ break;
+ case EXIT:
+ sb.append("exit");
+ break;
+ default:
+ sb.append("UNKNOWN(");
+ sb.append(mType);
+ sb.append(")");
+ break;
+ }
+ sb.append(" state: ");
+ sb.append(Integer.toHexString(mState));
+ sb.append(" }");
+ return sb.toString();
+ }
+ }
+
+ public StateMachine() {
+ this(0);
+ }
+
+ public StateMachine(@IntRange(from = 0) int initialState) {
+ mState = initialState;
+ AnnotationValidations.validate(IntRange.class, null, initialState, "from", 0);
+ mLastRequestedState = initialState;
+ }
+
+ /**
+ * @see #mLastRequestedState
+ */
+ public int getState() {
+ return mLastRequestedState;
+ }
+
+ protected int getCurrentState() {
+ return mState;
+ }
+
+ protected Command[] getCommands() {
+ final Command[] commands = new Command[mCommands.size()];
+ mCommands.toArray(commands);
+ return commands;
+ }
+
+ /**
+ * Add a handler for a specific state.
+ *
+ * @param state State which the given handler processes.
+ * @param handler A handler which runs entry, exit actions and processes events.
+ * @return Previous state handler if it's already registered, or {@code null}.
+ */
+ @Nullable public Handler addStateHandler(int state, @Nullable Handler handler) {
+ final Handler handlerOld = mStateHandlers.get(state);
+ mStateHandlers.put(state, handler);
+ return handlerOld;
+ }
+
+ /**
+ * Process an event. Search handler for a given event and {@link Handler#handle(int)}. If the
+ * handler cannot handle the event, delegate it to a handler for a parent of the given state.
+ *
+ * @param event Type of an event.
+ */
+ public void handle(int event, @Nullable Object param) {
+ int state = mState;
+ while (state != 0) {
+ final Handler h = mStateHandlers.get(state);
+ if (h != null && h.handle(event, param)) {
+ return;
+ }
+ state >>= 4;
+ }
+ }
+
+ protected void enter(@IntRange(from = 0) int state) {
+ AnnotationValidations.validate(IntRange.class, null, state, "from", 0);
+ final Handler h = mStateHandlers.get(state);
+ if (h != null) {
+ h.enter();
+ }
+ }
+
+ protected void exit(@IntRange(from = 0) int state) {
+ AnnotationValidations.validate(IntRange.class, null, state, "from", 0);
+ final Handler h = mStateHandlers.get(state);
+ if (h != null) {
+ h.exit();
+ }
+ }
+
+ /**
+ * @return {@code true} if a given sub state is a descendant of a given super state.
+ */
+ public static boolean isIn(int subState, int superState) {
+ while (subState > superState) {
+ subState >>= 4;
+ }
+ return subState == superState;
+ }
+
+ /**
+ * Check if the last requested state is a sub state of a given state.
+ *
+ * @return {@code true} if the last requested state (via {@link #transit(int)}) is a sub state
+ * of a given state.
+ */
+ public boolean isIn(int state) {
+ return isIn(mLastRequestedState, state);
+ }
+
+ /**
+ * Change state to the requested state.
+ *
+ * @param newState The new state that the state machine should be changed.
+ */
+ public void transit(@IntRange(from = 0) int newState) {
+ AnnotationValidations.validate(IntRange.class, null, newState, "from", 0);
+
+ // entry and exit action might start another transition, so this transit() function can be
+ // called recursively. In order to guarantee entry and exit actions in expected order,
+ // we first compute the sequence and push them into a queue, then process them later.
+ mCommands.add(Command.newCommit(newState));
+ if (mLastRequestedState == newState) {
+ mCommands.add(Command.newExit(newState));
+ mCommands.add(Command.newEnter(newState));
+ } else {
+ // mLastRequestedState to least common ancestor
+ for (int s = mLastRequestedState; !isIn(newState, s); s >>= 4) {
+ mCommands.add(Command.newExit(s));
+ }
+ // least common ancestor to newState
+ mTmp.clear();
+ for (int s = newState; !isIn(mLastRequestedState, s); s >>= 4) {
+ mTmp.add(s);
+ }
+ for (int i = mTmp.size() - 1; i >= 0; --i) {
+ mCommands.add(Command.newEnter(mTmp.get(i)));
+ }
+ }
+ mLastRequestedState = newState;
+ while (!mCommands.isEmpty()) {
+ final Command cmd = mCommands.remove();
+ switch (cmd.mType) {
+ case Command.EXIT:
+ exit(cmd.mState);
+ break;
+ case Command.ENTER:
+ enter(cmd.mState);
+ break;
+ case Command.COMMIT:
+ mState = cmd.mState;
+ break;
+ default:
+ Slog.e(TAG, "Unknown command type: " + cmd.mType);
+ break;
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/wm/utils/TEST_MAPPING b/services/core/java/com/android/server/wm/utils/TEST_MAPPING
new file mode 100644
index 000000000000..aa69d2a18948
--- /dev/null
+++ b/services/core/java/com/android/server/wm/utils/TEST_MAPPING
@@ -0,0 +1,18 @@
+{
+ "presubmit": [
+ {
+ "name": "WmTests",
+ "options": [
+ {
+ "include-filter": "com.android.server.wm.utils"
+ },
+ {
+ "include-annotation": "android.platform.test.annotations.Presubmit"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
+ }
+ ]
+}
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index f431250cf7e9..07819b9a18fc 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -31,6 +31,9 @@ cc_library_static {
"BroadcastRadio/convert.cpp",
"BroadcastRadio/regions.cpp",
"stats/SurfaceFlingerPuller.cpp",
+ "tvinput/BufferProducerThread.cpp",
+ "tvinput/JTvInputHal.cpp",
+ "tvinput/TvInputHal_hidl.cpp",
"com_android_server_adb_AdbDebuggingManager.cpp",
"com_android_server_am_BatteryStatsService.cpp",
"com_android_server_biometrics_SurfaceToNativeHandleConverter.cpp",
@@ -68,6 +71,7 @@ cc_library_static {
"com_android_server_PersistentDataBlockService.cpp",
"com_android_server_am_LowMemDetector.cpp",
"com_android_server_pm_PackageManagerShellCommandDataLoader.cpp",
+ "com_android_server_pm_Settings.cpp",
"com_android_server_sensor_SensorService.cpp",
"com_android_server_wm_TaskFpsCallbackController.cpp",
"onload.cpp",
@@ -149,7 +153,9 @@ cc_defaults {
"libpsi",
"libdataloader",
"libincfs",
+ "liblz4",
"android.hardware.audio.common@2.0",
+ "android.media.audio.common.types-V1-ndk",
"android.hardware.broadcastradio@1.0",
"android.hardware.broadcastradio@1.1",
"android.hardware.contexthub@1.0",
@@ -176,6 +182,7 @@ cc_defaults {
"android.hardware.power.stats-V1-ndk",
"android.hardware.thermal@1.0",
"android.hardware.tv.input@1.0",
+ "android.hardware.tv.input-V1-ndk",
"android.hardware.vibrator-V2-cpp",
"android.hardware.vibrator@1.0",
"android.hardware.vibrator@1.1",
@@ -197,6 +204,7 @@ cc_defaults {
static_libs: [
"android.hardware.broadcastradio@common-utils-1x-lib",
+ "libaidlcommonsupport",
],
product_variables: {
@@ -226,3 +234,26 @@ filegroup {
"com_android_server_app_GameManagerService.cpp",
],
}
+
+// Settings JNI library for unit tests.
+cc_library_shared {
+ name: "libservices.core.settings.testonly",
+ defaults: ["libservices.core-libs"],
+
+ cpp_std: "c++2a",
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wno-unused-parameter",
+ "-Wthread-safety",
+ ],
+
+ srcs: [
+ "com_android_server_pm_Settings.cpp",
+ "onload_settings.cpp",
+ ],
+
+ header_libs: [
+ "bionic_libc_platform_headers",
+ ],
+}
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 969056e7c8b4..145e0885b105 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -1578,6 +1578,12 @@ static std::vector<int32_t> getIntArray(JNIEnv* env, jintArray arr) {
return vec;
}
+static void nativeAddKeyRemapping(JNIEnv* env, jobject nativeImplObj, jint deviceId,
+ jint fromKeyCode, jint toKeyCode) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
+ im->getInputManager()->getReader().addKeyRemapping(deviceId, fromKeyCode, toKeyCode);
+}
+
static jboolean nativeHasKeys(JNIEnv* env, jobject nativeImplObj, jint deviceId, jint sourceMask,
jintArray keyCodes, jbooleanArray outFlags) {
NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
@@ -2360,6 +2366,7 @@ static const JNINativeMethod gInputManagerMethods[] = {
{"getScanCodeState", "(III)I", (void*)nativeGetScanCodeState},
{"getKeyCodeState", "(III)I", (void*)nativeGetKeyCodeState},
{"getSwitchState", "(III)I", (void*)nativeGetSwitchState},
+ {"addKeyRemapping", "(III)V", (void*)nativeAddKeyRemapping},
{"hasKeys", "(II[I[Z)Z", (void*)nativeHasKeys},
{"getKeyCodeForKeyLocation", "(II)I", (void*)nativeGetKeyCodeForKeyLocation},
{"createInputChannel", "(Ljava/lang/String;)Landroid/view/InputChannel;",
diff --git a/services/core/jni/com_android_server_pm_Settings.cpp b/services/core/jni/com_android_server_pm_Settings.cpp
new file mode 100644
index 000000000000..9633a115d718
--- /dev/null
+++ b/services/core/jni/com_android_server_pm_Settings.cpp
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define ATRACE_TAG ATRACE_TAG_ADB
+#define LOG_TAG "Settings-jni"
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/no_destructor.h>
+#include <core_jni_helpers.h>
+#include <lz4frame.h>
+#include <nativehelper/JNIHelp.h>
+
+#include <vector>
+
+namespace android {
+
+namespace {
+
+struct LZ4FCContextDeleter {
+ void operator()(LZ4F_cctx* cctx) { LZ4F_freeCompressionContext(cctx); }
+};
+
+static constexpr int LZ4_BUFFER_SIZE = 64 * 1024;
+
+static bool writeToFile(std::vector<char>& outBuffer, int fdOut) {
+ if (!android::base::WriteFully(fdOut, outBuffer.data(), outBuffer.size())) {
+ PLOG(ERROR) << "Error to write to output file";
+ return false;
+ }
+ outBuffer.clear();
+ return true;
+}
+
+static bool compressAndWriteLz4(LZ4F_cctx* context, std::vector<char>& inBuffer,
+ std::vector<char>& outBuffer, int fdOut) {
+ auto inSize = inBuffer.size();
+ if (inSize > 0) {
+ auto prvSize = outBuffer.size();
+ auto outSize = LZ4F_compressBound(inSize, nullptr);
+ outBuffer.resize(prvSize + outSize);
+ auto rc = LZ4F_compressUpdate(context, outBuffer.data() + prvSize, outSize, inBuffer.data(),
+ inSize, nullptr);
+ if (LZ4F_isError(rc)) {
+ LOG(ERROR) << "LZ4F_compressUpdate failed: " << LZ4F_getErrorName(rc);
+ return false;
+ }
+ outBuffer.resize(prvSize + rc);
+ }
+
+ if (outBuffer.size() > LZ4_BUFFER_SIZE) {
+ return writeToFile(outBuffer, fdOut);
+ }
+
+ return true;
+}
+
+static jboolean nativeCompressLz4(JNIEnv* env, jclass klass, jint fdIn, jint fdOut) {
+ LZ4F_cctx* cctx;
+ if (LZ4F_createCompressionContext(&cctx, LZ4F_VERSION) != 0) {
+ LOG(ERROR) << "Failed to initialize LZ4 compression context.";
+ return false;
+ }
+ std::unique_ptr<LZ4F_cctx, LZ4FCContextDeleter> context(cctx);
+
+ std::vector<char> inBuffer, outBuffer;
+ inBuffer.reserve(LZ4_BUFFER_SIZE);
+ outBuffer.reserve(2 * LZ4_BUFFER_SIZE);
+
+ LZ4F_preferences_t prefs;
+
+ memset(&prefs, 0, sizeof(prefs));
+
+ // Set compression parameters.
+ prefs.autoFlush = 0;
+ prefs.compressionLevel = 0;
+ prefs.frameInfo.blockMode = LZ4F_blockLinked;
+ prefs.frameInfo.blockSizeID = LZ4F_default;
+ prefs.frameInfo.blockChecksumFlag = LZ4F_noBlockChecksum;
+ prefs.frameInfo.contentChecksumFlag = LZ4F_contentChecksumEnabled;
+ prefs.favorDecSpeed = 0;
+
+ struct stat sb;
+ if (fstat(fdIn, &sb) == -1) {
+ PLOG(ERROR) << "Failed to obtain input file size.";
+ return false;
+ }
+ prefs.frameInfo.contentSize = sb.st_size;
+
+ // Write header first.
+ outBuffer.resize(LZ4F_HEADER_SIZE_MAX);
+ auto rc = LZ4F_compressBegin(context.get(), outBuffer.data(), outBuffer.size(), &prefs);
+ if (LZ4F_isError(rc)) {
+ LOG(ERROR) << "LZ4F_compressBegin failed: " << LZ4F_getErrorName(rc);
+ return false;
+ }
+ outBuffer.resize(rc);
+
+ bool eof = false;
+ while (!eof) {
+ constexpr auto capacity = LZ4_BUFFER_SIZE;
+ inBuffer.resize(capacity);
+ auto read = TEMP_FAILURE_RETRY(::read(fdIn, inBuffer.data(), inBuffer.size()));
+ if (read < 0) {
+ PLOG(ERROR) << "Failed to read from input file.";
+ return false;
+ }
+
+ inBuffer.resize(read);
+
+ if (read == 0) {
+ eof = true;
+ }
+
+ if (!compressAndWriteLz4(context.get(), inBuffer, outBuffer, fdOut)) {
+ return false;
+ }
+ }
+
+ // Footer.
+ auto prvSize = outBuffer.size();
+ outBuffer.resize(outBuffer.capacity());
+ rc = LZ4F_compressEnd(context.get(), outBuffer.data() + prvSize, outBuffer.size() - prvSize,
+ nullptr);
+ if (LZ4F_isError(rc)) {
+ LOG(ERROR) << "LZ4F_compressEnd failed: " << LZ4F_getErrorName(rc);
+ return false;
+ }
+ outBuffer.resize(prvSize + rc);
+
+ if (!writeToFile(outBuffer, fdOut)) {
+ return false;
+ }
+
+ return true;
+}
+
+static const JNINativeMethod method_table[] = {
+ {"nativeCompressLz4", "(II)Z", (void*)nativeCompressLz4},
+};
+
+} // namespace
+
+int register_android_server_com_android_server_pm_Settings(JNIEnv* env) {
+ return jniRegisterNativeMethods(env, "com/android/server/pm/Settings", method_table,
+ NELEM(method_table));
+}
+
+} // namespace android
diff --git a/services/core/jni/com_android_server_tv_TvInputHal.cpp b/services/core/jni/com_android_server_tv_TvInputHal.cpp
index a5311f33eb36..a8d2f4e9be5b 100644
--- a/services/core/jni/com_android_server_tv_TvInputHal.cpp
+++ b/services/core/jni/com_android_server_tv_TvInputHal.cpp
@@ -18,577 +18,15 @@
//#define LOG_NDEBUG 0
-#include "android_os_MessageQueue.h"
-#include "android_runtime/AndroidRuntime.h"
-#include "android_runtime/android_view_Surface.h"
-#include <nativehelper/JNIHelp.h>
-#include "jni.h"
+#include "tvinput/JTvInputHal.h"
-#include <android/hardware/tv/input/1.0/ITvInputCallback.h>
-#include <android/hardware/tv/input/1.0/ITvInput.h>
-#include <android/hardware/tv/input/1.0/types.h>
-#include <gui/Surface.h>
-#include <utils/Errors.h>
-#include <utils/KeyedVector.h>
-#include <utils/Log.h>
-#include <utils/Looper.h>
-#include <utils/NativeHandle.h>
-#include <hardware/tv_input.h>
-
-using ::android::hardware::audio::common::V2_0::AudioDevice;
-using ::android::hardware::tv::input::V1_0::ITvInput;
-using ::android::hardware::tv::input::V1_0::ITvInputCallback;
-using ::android::hardware::tv::input::V1_0::Result;
-using ::android::hardware::tv::input::V1_0::TvInputDeviceInfo;
-using ::android::hardware::tv::input::V1_0::TvInputEvent;
-using ::android::hardware::tv::input::V1_0::TvInputEventType;
-using ::android::hardware::tv::input::V1_0::TvInputType;
-using ::android::hardware::tv::input::V1_0::TvStreamConfig;
-using ::android::hardware::Return;
-using ::android::hardware::Void;
-using ::android::hardware::hidl_vec;
-using ::android::hardware::hidl_string;
+gTvInputHalClassInfoType gTvInputHalClassInfo;
+gTvStreamConfigClassInfoType gTvStreamConfigClassInfo;
+gTvStreamConfigBuilderClassInfoType gTvStreamConfigBuilderClassInfo;
+gTvInputHardwareInfoBuilderClassInfoType gTvInputHardwareInfoBuilderClassInfo;
namespace android {
-static struct {
- jmethodID deviceAvailable;
- jmethodID deviceUnavailable;
- jmethodID streamConfigsChanged;
- jmethodID firstFrameCaptured;
-} gTvInputHalClassInfo;
-
-static struct {
- jclass clazz;
-} gTvStreamConfigClassInfo;
-
-static struct {
- jclass clazz;
-
- jmethodID constructor;
- jmethodID streamId;
- jmethodID type;
- jmethodID maxWidth;
- jmethodID maxHeight;
- jmethodID generation;
- jmethodID build;
-} gTvStreamConfigBuilderClassInfo;
-
-static struct {
- jclass clazz;
-
- jmethodID constructor;
- jmethodID deviceId;
- jmethodID type;
- jmethodID hdmiPortId;
- jmethodID cableConnectionStatus;
- jmethodID audioType;
- jmethodID audioAddress;
- jmethodID build;
-} gTvInputHardwareInfoBuilderClassInfo;
-
-////////////////////////////////////////////////////////////////////////////////
-
-class BufferProducerThread : public Thread {
-public:
- BufferProducerThread(tv_input_device_t* device, int deviceId, const tv_stream_t* stream);
-
- virtual status_t readyToRun();
-
- void setSurface(const sp<Surface>& surface);
- void onCaptured(uint32_t seq, bool succeeded);
- void shutdown();
-
-private:
- Mutex mLock;
- Condition mCondition;
- sp<Surface> mSurface;
- tv_input_device_t* mDevice;
- int mDeviceId;
- tv_stream_t mStream;
- sp<ANativeWindowBuffer_t> mBuffer;
- enum {
- CAPTURING,
- CAPTURED,
- RELEASED,
- } mBufferState;
- uint32_t mSeq;
- bool mShutdown;
-
- virtual bool threadLoop();
-
- void setSurfaceLocked(const sp<Surface>& surface);
-};
-
-BufferProducerThread::BufferProducerThread(
- tv_input_device_t* device, int deviceId, const tv_stream_t* stream)
- : Thread(false),
- mDevice(device),
- mDeviceId(deviceId),
- mBuffer(NULL),
- mBufferState(RELEASED),
- mSeq(0u),
- mShutdown(false) {
- memcpy(&mStream, stream, sizeof(mStream));
-}
-
-status_t BufferProducerThread::readyToRun() {
- sp<ANativeWindow> anw(mSurface);
- status_t err = native_window_set_usage(anw.get(), mStream.buffer_producer.usage);
- if (err != NO_ERROR) {
- return err;
- }
- err = native_window_set_buffers_dimensions(
- anw.get(), mStream.buffer_producer.width, mStream.buffer_producer.height);
- if (err != NO_ERROR) {
- return err;
- }
- err = native_window_set_buffers_format(anw.get(), mStream.buffer_producer.format);
- if (err != NO_ERROR) {
- return err;
- }
- return NO_ERROR;
-}
-
-void BufferProducerThread::setSurface(const sp<Surface>& surface) {
- Mutex::Autolock autoLock(&mLock);
- setSurfaceLocked(surface);
-}
-
-void BufferProducerThread::setSurfaceLocked(const sp<Surface>& surface) {
- if (surface == mSurface) {
- return;
- }
-
- if (mBufferState == CAPTURING) {
- mDevice->cancel_capture(mDevice, mDeviceId, mStream.stream_id, mSeq);
- }
- while (mBufferState == CAPTURING) {
- status_t err = mCondition.waitRelative(mLock, s2ns(1));
- if (err != NO_ERROR) {
- ALOGE("error %d while wating for buffer state to change.", err);
- break;
- }
- }
- mBuffer.clear();
- mBufferState = RELEASED;
-
- mSurface = surface;
- mCondition.broadcast();
-}
-
-void BufferProducerThread::onCaptured(uint32_t seq, bool succeeded) {
- Mutex::Autolock autoLock(&mLock);
- if (seq != mSeq) {
- ALOGW("Incorrect sequence value: expected %u actual %u", mSeq, seq);
- }
- if (mBufferState != CAPTURING) {
- ALOGW("mBufferState != CAPTURING : instead %d", mBufferState);
- }
- if (succeeded) {
- mBufferState = CAPTURED;
- } else {
- mBuffer.clear();
- mBufferState = RELEASED;
- }
- mCondition.broadcast();
-}
-
-void BufferProducerThread::shutdown() {
- Mutex::Autolock autoLock(&mLock);
- mShutdown = true;
- setSurfaceLocked(NULL);
- requestExitAndWait();
-}
-
-bool BufferProducerThread::threadLoop() {
- Mutex::Autolock autoLock(&mLock);
-
- status_t err = NO_ERROR;
- if (mSurface == NULL) {
- err = mCondition.waitRelative(mLock, s2ns(1));
- // It's OK to time out here.
- if (err != NO_ERROR && err != TIMED_OUT) {
- ALOGE("error %d while wating for non-null surface to be set", err);
- return false;
- }
- return true;
- }
- sp<ANativeWindow> anw(mSurface);
- while (mBufferState == CAPTURING) {
- err = mCondition.waitRelative(mLock, s2ns(1));
- if (err != NO_ERROR) {
- ALOGE("error %d while wating for buffer state to change.", err);
- return false;
- }
- }
- if (mBufferState == CAPTURED && anw != NULL) {
- err = anw->queueBuffer(anw.get(), mBuffer.get(), -1);
- if (err != NO_ERROR) {
- ALOGE("error %d while queueing buffer to surface", err);
- return false;
- }
- mBuffer.clear();
- mBufferState = RELEASED;
- }
- if (mBuffer == NULL && !mShutdown && anw != NULL) {
- ANativeWindowBuffer_t* buffer = NULL;
- err = native_window_dequeue_buffer_and_wait(anw.get(), &buffer);
- if (err != NO_ERROR) {
- ALOGE("error %d while dequeueing buffer to surface", err);
- return false;
- }
- mBuffer = buffer;
- mBufferState = CAPTURING;
- mDevice->request_capture(mDevice, mDeviceId, mStream.stream_id,
- buffer->handle, ++mSeq);
- }
-
- return true;
-}
-
-////////////////////////////////////////////////////////////////////////////////
-
-class JTvInputHal {
-public:
- ~JTvInputHal();
-
- static JTvInputHal* createInstance(JNIEnv* env, jobject thiz, const sp<Looper>& looper);
-
- int addOrUpdateStream(int deviceId, int streamId, const sp<Surface>& surface);
- int removeStream(int deviceId, int streamId);
- const hidl_vec<TvStreamConfig> getStreamConfigs(int deviceId);
-
- void onDeviceAvailable(const TvInputDeviceInfo& info);
- void onDeviceUnavailable(int deviceId);
- void onStreamConfigurationsChanged(int deviceId, int cableConnectionStatus);
- void onCaptured(int deviceId, int streamId, uint32_t seq, bool succeeded);
-
-private:
- // Connection between a surface and a stream.
- class Connection {
- public:
- Connection() {}
-
- sp<Surface> mSurface;
- tv_stream_type_t mStreamType;
-
- // Only valid when mStreamType == TV_STREAM_TYPE_INDEPENDENT_VIDEO_SOURCE
- sp<NativeHandle> mSourceHandle;
- // Only valid when mStreamType == TV_STREAM_TYPE_BUFFER_PRODUCER
- sp<BufferProducerThread> mThread;
- };
-
- class NotifyHandler : public MessageHandler {
- public:
- NotifyHandler(JTvInputHal* hal, const TvInputEvent& event);
-
- virtual void handleMessage(const Message& message);
-
- private:
- TvInputEvent mEvent;
- JTvInputHal* mHal;
- };
-
- class TvInputCallback : public ITvInputCallback {
- public:
- explicit TvInputCallback(JTvInputHal* hal);
- Return<void> notify(const TvInputEvent& event) override;
- private:
- JTvInputHal* mHal;
- };
-
- JTvInputHal(JNIEnv* env, jobject thiz, sp<ITvInput> tvInput, const sp<Looper>& looper);
-
- Mutex mLock;
- Mutex mStreamLock;
- jweak mThiz;
- sp<Looper> mLooper;
-
- KeyedVector<int, KeyedVector<int, Connection> > mConnections;
-
- sp<ITvInput> mTvInput;
- sp<ITvInputCallback> mTvInputCallback;
-};
-
-JTvInputHal::JTvInputHal(JNIEnv* env, jobject thiz, sp<ITvInput> tvInput,
- const sp<Looper>& looper) {
- mThiz = env->NewWeakGlobalRef(thiz);
- mTvInput = tvInput;
- mLooper = looper;
- mTvInputCallback = new TvInputCallback(this);
- mTvInput->setCallback(mTvInputCallback);
-}
-
-JTvInputHal::~JTvInputHal() {
- mTvInput->setCallback(nullptr);
- JNIEnv* env = AndroidRuntime::getJNIEnv();
- env->DeleteWeakGlobalRef(mThiz);
- mThiz = NULL;
-}
-
-JTvInputHal* JTvInputHal::createInstance(JNIEnv* env, jobject thiz, const sp<Looper>& looper) {
- // TODO(b/31632518)
- sp<ITvInput> tvInput = ITvInput::getService();
- if (tvInput == nullptr) {
- ALOGE("Couldn't get tv.input service.");
- return nullptr;
- }
-
- return new JTvInputHal(env, thiz, tvInput, looper);
-}
-
-int JTvInputHal::addOrUpdateStream(int deviceId, int streamId, const sp<Surface>& surface) {
- Mutex::Autolock autoLock(&mStreamLock);
- KeyedVector<int, Connection>& connections = mConnections.editValueFor(deviceId);
- if (connections.indexOfKey(streamId) < 0) {
- connections.add(streamId, Connection());
- }
- Connection& connection = connections.editValueFor(streamId);
- if (connection.mSurface == surface) {
- // Nothing to do
- return NO_ERROR;
- }
- // Clear the surface in the connection.
- if (connection.mSurface != NULL) {
- if (connection.mStreamType == TV_STREAM_TYPE_INDEPENDENT_VIDEO_SOURCE) {
- if (Surface::isValid(connection.mSurface)) {
- connection.mSurface->setSidebandStream(NULL);
- }
- }
- connection.mSurface.clear();
- }
- if (connection.mSourceHandle == NULL && connection.mThread == NULL) {
- // Need to configure stream
- Result result = Result::UNKNOWN;
- hidl_vec<TvStreamConfig> list;
- mTvInput->getStreamConfigurations(deviceId,
- [&result, &list](Result res, hidl_vec<TvStreamConfig> configs) {
- result = res;
- if (res == Result::OK) {
- list = configs;
- }
- });
- if (result != Result::OK) {
- ALOGE("Couldn't get stream configs for device id:%d result:%d", deviceId, result);
- return UNKNOWN_ERROR;
- }
- int configIndex = -1;
- for (size_t i = 0; i < list.size(); ++i) {
- if (list[i].streamId == streamId) {
- configIndex = i;
- break;
- }
- }
- if (configIndex == -1) {
- ALOGE("Cannot find a config with given stream ID: %d", streamId);
- return BAD_VALUE;
- }
- connection.mStreamType = TV_STREAM_TYPE_INDEPENDENT_VIDEO_SOURCE;
-
- result = Result::UNKNOWN;
- const native_handle_t* sidebandStream;
- mTvInput->openStream(deviceId, streamId,
- [&result, &sidebandStream](Result res, const native_handle_t* handle) {
- result = res;
- if (res == Result::OK) {
- if (handle) {
- sidebandStream = native_handle_clone(handle);
- } else {
- result = Result::UNKNOWN;
- }
- }
- });
- if (result != Result::OK) {
- ALOGE("Couldn't open stream. device id:%d stream id:%d result:%d", deviceId, streamId,
- result);
- return UNKNOWN_ERROR;
- }
- connection.mSourceHandle = NativeHandle::create((native_handle_t*)sidebandStream, true);
- }
- connection.mSurface = surface;
- if (connection.mSurface != nullptr) {
- connection.mSurface->setSidebandStream(connection.mSourceHandle);
- }
- return NO_ERROR;
-}
-
-int JTvInputHal::removeStream(int deviceId, int streamId) {
- Mutex::Autolock autoLock(&mStreamLock);
- KeyedVector<int, Connection>& connections = mConnections.editValueFor(deviceId);
- if (connections.indexOfKey(streamId) < 0) {
- return BAD_VALUE;
- }
- Connection& connection = connections.editValueFor(streamId);
- if (connection.mSurface == NULL) {
- // Nothing to do
- return NO_ERROR;
- }
- if (Surface::isValid(connection.mSurface)) {
- connection.mSurface->setSidebandStream(NULL);
- }
- connection.mSurface.clear();
- if (connection.mThread != NULL) {
- connection.mThread->shutdown();
- connection.mThread.clear();
- }
- if (mTvInput->closeStream(deviceId, streamId) != Result::OK) {
- ALOGE("Couldn't close stream. device id:%d stream id:%d", deviceId, streamId);
- return BAD_VALUE;
- }
- if (connection.mSourceHandle != NULL) {
- connection.mSourceHandle.clear();
- }
- return NO_ERROR;
-}
-
-const hidl_vec<TvStreamConfig> JTvInputHal::getStreamConfigs(int deviceId) {
- Result result = Result::UNKNOWN;
- hidl_vec<TvStreamConfig> list;
- mTvInput->getStreamConfigurations(deviceId,
- [&result, &list](Result res, hidl_vec<TvStreamConfig> configs) {
- result = res;
- if (res == Result::OK) {
- list = configs;
- }
- });
- if (result != Result::OK) {
- ALOGE("Couldn't get stream configs for device id:%d result:%d", deviceId, result);
- }
- return list;
-}
-
-void JTvInputHal::onDeviceAvailable(const TvInputDeviceInfo& info) {
- {
- Mutex::Autolock autoLock(&mLock);
- mConnections.add(info.deviceId, KeyedVector<int, Connection>());
- }
- JNIEnv* env = AndroidRuntime::getJNIEnv();
-
- jobject builder = env->NewObject(
- gTvInputHardwareInfoBuilderClassInfo.clazz,
- gTvInputHardwareInfoBuilderClassInfo.constructor);
- env->CallObjectMethod(
- builder, gTvInputHardwareInfoBuilderClassInfo.deviceId, info.deviceId);
- env->CallObjectMethod(
- builder, gTvInputHardwareInfoBuilderClassInfo.type, info.type);
- if (info.type == TvInputType::HDMI) {
- env->CallObjectMethod(
- builder, gTvInputHardwareInfoBuilderClassInfo.hdmiPortId, info.portId);
- }
- env->CallObjectMethod(
- builder, gTvInputHardwareInfoBuilderClassInfo.cableConnectionStatus,
- info.cableConnectionStatus);
- env->CallObjectMethod(
- builder, gTvInputHardwareInfoBuilderClassInfo.audioType, info.audioType);
- if (info.audioType != AudioDevice::NONE) {
- uint8_t buffer[info.audioAddress.size() + 1];
- memcpy(buffer, info.audioAddress.data(), info.audioAddress.size());
- buffer[info.audioAddress.size()] = '\0';
- jstring audioAddress = env->NewStringUTF(reinterpret_cast<const char *>(buffer));
- env->CallObjectMethod(
- builder, gTvInputHardwareInfoBuilderClassInfo.audioAddress, audioAddress);
- env->DeleteLocalRef(audioAddress);
- }
-
- jobject infoObject = env->CallObjectMethod(builder, gTvInputHardwareInfoBuilderClassInfo.build);
-
- env->CallVoidMethod(
- mThiz,
- gTvInputHalClassInfo.deviceAvailable,
- infoObject);
-
- env->DeleteLocalRef(builder);
- env->DeleteLocalRef(infoObject);
-}
-
-void JTvInputHal::onDeviceUnavailable(int deviceId) {
- {
- Mutex::Autolock autoLock(&mLock);
- KeyedVector<int, Connection>& connections = mConnections.editValueFor(deviceId);
- for (size_t i = 0; i < connections.size(); ++i) {
- removeStream(deviceId, connections.keyAt(i));
- }
- connections.clear();
- mConnections.removeItem(deviceId);
- }
- JNIEnv* env = AndroidRuntime::getJNIEnv();
- env->CallVoidMethod(
- mThiz,
- gTvInputHalClassInfo.deviceUnavailable,
- deviceId);
-}
-
-void JTvInputHal::onStreamConfigurationsChanged(int deviceId, int cableConnectionStatus) {
- {
- Mutex::Autolock autoLock(&mLock);
- KeyedVector<int, Connection>& connections = mConnections.editValueFor(deviceId);
- for (size_t i = 0; i < connections.size(); ++i) {
- removeStream(deviceId, connections.keyAt(i));
- }
- connections.clear();
- }
- JNIEnv* env = AndroidRuntime::getJNIEnv();
- env->CallVoidMethod(mThiz, gTvInputHalClassInfo.streamConfigsChanged, deviceId,
- cableConnectionStatus);
-}
-
-void JTvInputHal::onCaptured(int deviceId, int streamId, uint32_t seq, bool succeeded) {
- sp<BufferProducerThread> thread;
- {
- Mutex::Autolock autoLock(&mLock);
- KeyedVector<int, Connection>& connections = mConnections.editValueFor(deviceId);
- Connection& connection = connections.editValueFor(streamId);
- if (connection.mThread == NULL) {
- ALOGE("capture thread not existing.");
- return;
- }
- thread = connection.mThread;
- }
- thread->onCaptured(seq, succeeded);
- if (seq == 0) {
- JNIEnv* env = AndroidRuntime::getJNIEnv();
- env->CallVoidMethod(
- mThiz,
- gTvInputHalClassInfo.firstFrameCaptured,
- deviceId,
- streamId);
- }
-}
-
-JTvInputHal::NotifyHandler::NotifyHandler(JTvInputHal* hal, const TvInputEvent& event) {
- mHal = hal;
- mEvent = event;
-}
-
-void JTvInputHal::NotifyHandler::handleMessage(const Message& message) {
- switch (mEvent.type) {
- case TvInputEventType::DEVICE_AVAILABLE: {
- mHal->onDeviceAvailable(mEvent.deviceInfo);
- } break;
- case TvInputEventType::DEVICE_UNAVAILABLE: {
- mHal->onDeviceUnavailable(mEvent.deviceInfo.deviceId);
- } break;
- case TvInputEventType::STREAM_CONFIGURATIONS_CHANGED: {
- int cableConnectionStatus = static_cast<int>(mEvent.deviceInfo.cableConnectionStatus);
- mHal->onStreamConfigurationsChanged(mEvent.deviceInfo.deviceId, cableConnectionStatus);
- } break;
- default:
- ALOGE("Unrecognizable event");
- }
-}
-
-JTvInputHal::TvInputCallback::TvInputCallback(JTvInputHal* hal) {
- mHal = hal;
-}
-
-Return<void> JTvInputHal::TvInputCallback::notify(const TvInputEvent& event) {
- mHal->mLooper->sendMessage(new NotifyHandler(mHal, event), static_cast<int>(event.type));
- return Void();
-}
-
-////////////////////////////////////////////////////////////////////////////////
-
static jlong nativeOpen(JNIEnv* env, jobject thiz, jobject messageQueueObj) {
sp<MessageQueue> messageQueue =
android_os_MessageQueue_getMessageQueue(env, messageQueueObj);
@@ -617,7 +55,7 @@ static int nativeRemoveStream(JNIEnv* env, jclass clazz,
static jobjectArray nativeGetStreamConfigs(JNIEnv* env, jclass clazz,
jlong ptr, jint deviceId, jint generation) {
JTvInputHal* tvInputHal = (JTvInputHal*)ptr;
- const hidl_vec<TvStreamConfig> configs = tvInputHal->getStreamConfigs(deviceId);
+ const std::vector<AidlTvStreamConfig> configs = tvInputHal->getStreamConfigs(deviceId);
jobjectArray result = env->NewObjectArray(configs.size(), gTvStreamConfigClassInfo.clazz, NULL);
for (size_t i = 0; i < configs.size(); ++i) {
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index 00f851f9f4ff..184505713420 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -56,6 +56,7 @@ int register_android_server_am_LowMemDetector(JNIEnv* env);
int register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl(JNIEnv* env);
int register_com_android_server_soundtrigger_middleware_ExternalCaptureStateTracker(JNIEnv* env);
int register_android_server_com_android_server_pm_PackageManagerShellCommandDataLoader(JNIEnv* env);
+int register_android_server_com_android_server_pm_Settings(JNIEnv* env);
int register_android_server_AdbDebuggingManager(JNIEnv* env);
int register_android_server_FaceService(JNIEnv* env);
int register_android_server_GpuService(JNIEnv* env);
@@ -114,6 +115,7 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl(env);
register_com_android_server_soundtrigger_middleware_ExternalCaptureStateTracker(env);
register_android_server_com_android_server_pm_PackageManagerShellCommandDataLoader(env);
+ register_android_server_com_android_server_pm_Settings(env);
register_android_server_AdbDebuggingManager(env);
register_android_server_FaceService(env);
register_android_server_GpuService(env);
diff --git a/services/core/jni/onload_settings.cpp b/services/core/jni/onload_settings.cpp
new file mode 100644
index 000000000000..b21c34a9f9b9
--- /dev/null
+++ b/services/core/jni/onload_settings.cpp
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "jni.h"
+#include "utils/Log.h"
+
+namespace android {
+int register_android_server_com_android_server_pm_Settings(JNIEnv* env);
+};
+
+using namespace android;
+
+extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) {
+ JNIEnv* env = NULL;
+ jint result = -1;
+
+ if (vm->GetEnv((void**)&env, JNI_VERSION_1_4) != JNI_OK) {
+ ALOGE("GetEnv failed!");
+ return result;
+ }
+ ALOG_ASSERT(env, "Could not retrieve the env!");
+
+ register_android_server_com_android_server_pm_Settings(env);
+
+ return JNI_VERSION_1_4;
+}
diff --git a/services/core/jni/tvinput/BufferProducerThread.cpp b/services/core/jni/tvinput/BufferProducerThread.cpp
new file mode 100644
index 000000000000..f39dcee36ec2
--- /dev/null
+++ b/services/core/jni/tvinput/BufferProducerThread.cpp
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "BufferProducerThread.h"
+
+namespace android {
+
+BufferProducerThread::BufferProducerThread(tv_input_device_t* device, int deviceId,
+ const tv_stream_t* stream)
+ : Thread(false),
+ mDevice(device),
+ mDeviceId(deviceId),
+ mBuffer(NULL),
+ mBufferState(RELEASED),
+ mSeq(0u),
+ mShutdown(false) {
+ memcpy(&mStream, stream, sizeof(mStream));
+}
+
+status_t BufferProducerThread::readyToRun() {
+ sp<ANativeWindow> anw(mSurface);
+ status_t err = native_window_set_usage(anw.get(), mStream.buffer_producer.usage);
+ if (err != NO_ERROR) {
+ return err;
+ }
+ err = native_window_set_buffers_dimensions(anw.get(), mStream.buffer_producer.width,
+ mStream.buffer_producer.height);
+ if (err != NO_ERROR) {
+ return err;
+ }
+ err = native_window_set_buffers_format(anw.get(), mStream.buffer_producer.format);
+ if (err != NO_ERROR) {
+ return err;
+ }
+ return NO_ERROR;
+}
+
+void BufferProducerThread::setSurface(const sp<Surface>& surface) {
+ Mutex::Autolock autoLock(&mLock);
+ setSurfaceLocked(surface);
+}
+
+void BufferProducerThread::setSurfaceLocked(const sp<Surface>& surface) {
+ if (surface == mSurface) {
+ return;
+ }
+
+ if (mBufferState == CAPTURING) {
+ mDevice->cancel_capture(mDevice, mDeviceId, mStream.stream_id, mSeq);
+ }
+ while (mBufferState == CAPTURING) {
+ status_t err = mCondition.waitRelative(mLock, s2ns(1));
+ if (err != NO_ERROR) {
+ ALOGE("error %d while wating for buffer state to change.", err);
+ break;
+ }
+ }
+ mBuffer.clear();
+ mBufferState = RELEASED;
+
+ mSurface = surface;
+ mCondition.broadcast();
+}
+
+void BufferProducerThread::onCaptured(uint32_t seq, bool succeeded) {
+ Mutex::Autolock autoLock(&mLock);
+ if (seq != mSeq) {
+ ALOGW("Incorrect sequence value: expected %u actual %u", mSeq, seq);
+ }
+ if (mBufferState != CAPTURING) {
+ ALOGW("mBufferState != CAPTURING : instead %d", mBufferState);
+ }
+ if (succeeded) {
+ mBufferState = CAPTURED;
+ } else {
+ mBuffer.clear();
+ mBufferState = RELEASED;
+ }
+ mCondition.broadcast();
+}
+
+void BufferProducerThread::shutdown() {
+ Mutex::Autolock autoLock(&mLock);
+ mShutdown = true;
+ setSurfaceLocked(NULL);
+ requestExitAndWait();
+}
+
+bool BufferProducerThread::threadLoop() {
+ Mutex::Autolock autoLock(&mLock);
+
+ status_t err = NO_ERROR;
+ if (mSurface == NULL) {
+ err = mCondition.waitRelative(mLock, s2ns(1));
+ // It's OK to time out here.
+ if (err != NO_ERROR && err != TIMED_OUT) {
+ ALOGE("error %d while wating for non-null surface to be set", err);
+ return false;
+ }
+ return true;
+ }
+ sp<ANativeWindow> anw(mSurface);
+ while (mBufferState == CAPTURING) {
+ err = mCondition.waitRelative(mLock, s2ns(1));
+ if (err != NO_ERROR) {
+ ALOGE("error %d while wating for buffer state to change.", err);
+ return false;
+ }
+ }
+ if (mBufferState == CAPTURED && anw != NULL) {
+ err = anw->queueBuffer(anw.get(), mBuffer.get(), -1);
+ if (err != NO_ERROR) {
+ ALOGE("error %d while queueing buffer to surface", err);
+ return false;
+ }
+ mBuffer.clear();
+ mBufferState = RELEASED;
+ }
+ if (mBuffer == NULL && !mShutdown && anw != NULL) {
+ ANativeWindowBuffer_t* buffer = NULL;
+ err = native_window_dequeue_buffer_and_wait(anw.get(), &buffer);
+ if (err != NO_ERROR) {
+ ALOGE("error %d while dequeueing buffer to surface", err);
+ return false;
+ }
+ mBuffer = buffer;
+ mBufferState = CAPTURING;
+ mDevice->request_capture(mDevice, mDeviceId, mStream.stream_id, buffer->handle, ++mSeq);
+ }
+
+ return true;
+}
+
+} // namespace android
diff --git a/services/core/jni/tvinput/BufferProducerThread.h b/services/core/jni/tvinput/BufferProducerThread.h
new file mode 100644
index 000000000000..2e0fa917e87e
--- /dev/null
+++ b/services/core/jni/tvinput/BufferProducerThread.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <gui/Surface.h>
+#include <hardware/tv_input.h>
+#include <utils/Thread.h>
+
+namespace android {
+
+class BufferProducerThread : public Thread {
+public:
+ BufferProducerThread(tv_input_device_t* device, int deviceId, const tv_stream_t* stream);
+
+ virtual status_t readyToRun();
+
+ void setSurface(const sp<Surface>& surface);
+ void onCaptured(uint32_t seq, bool succeeded);
+ void shutdown();
+
+private:
+ Mutex mLock;
+ Condition mCondition;
+ sp<Surface> mSurface;
+ tv_input_device_t* mDevice;
+ int mDeviceId;
+ tv_stream_t mStream;
+ sp<ANativeWindowBuffer_t> mBuffer;
+ enum {
+ CAPTURING,
+ CAPTURED,
+ RELEASED,
+ } mBufferState;
+ uint32_t mSeq;
+ bool mShutdown;
+
+ virtual bool threadLoop();
+
+ void setSurfaceLocked(const sp<Surface>& surface);
+};
+
+} // namespace android
diff --git a/services/core/jni/tvinput/JTvInputHal.cpp b/services/core/jni/tvinput/JTvInputHal.cpp
new file mode 100644
index 000000000000..3453cbdcb4de
--- /dev/null
+++ b/services/core/jni/tvinput/JTvInputHal.cpp
@@ -0,0 +1,381 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "JTvInputHal.h"
+
+namespace android {
+
+JTvInputHal::JTvInputHal(JNIEnv* env, jobject thiz, std::shared_ptr<ITvInputWrapper> tvInput,
+ const sp<Looper>& looper) {
+ mThiz = env->NewWeakGlobalRef(thiz);
+ mTvInput = tvInput;
+ mLooper = looper;
+ mTvInputCallback = ::ndk::SharedRefBase::make<TvInputCallback>(this);
+ mTvInput->setCallback(mTvInputCallback);
+}
+
+JTvInputHal::~JTvInputHal() {
+ mTvInput->setCallback(nullptr);
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+ env->DeleteWeakGlobalRef(mThiz);
+ mThiz = NULL;
+}
+
+JTvInputHal* JTvInputHal::createInstance(JNIEnv* env, jobject thiz, const sp<Looper>& looper) {
+ sp<HidlITvInput> hidlITvInput = HidlITvInput::getService();
+ if (hidlITvInput != nullptr) {
+ ALOGD("tv.input service is HIDL.");
+ return new JTvInputHal(env, thiz,
+ std::shared_ptr<ITvInputWrapper>(new ITvInputWrapper(hidlITvInput)),
+ looper);
+ }
+ std::shared_ptr<AidlITvInput> aidlITvInput = nullptr;
+ if (AServiceManager_isDeclared(TV_INPUT_AIDL_SERVICE_NAME)) {
+ ::ndk::SpAIBinder binder(AServiceManager_waitForService(TV_INPUT_AIDL_SERVICE_NAME));
+ aidlITvInput = AidlITvInput::fromBinder(binder);
+ }
+ if (aidlITvInput == nullptr) {
+ ALOGE("Couldn't get tv.input service.");
+ return nullptr;
+ }
+ return new JTvInputHal(env, thiz,
+ std::shared_ptr<ITvInputWrapper>(new ITvInputWrapper(aidlITvInput)),
+ looper);
+}
+
+int JTvInputHal::addOrUpdateStream(int deviceId, int streamId, const sp<Surface>& surface) {
+ Mutex::Autolock autoLock(&mStreamLock);
+ KeyedVector<int, Connection>& connections = mConnections.editValueFor(deviceId);
+ if (connections.indexOfKey(streamId) < 0) {
+ connections.add(streamId, Connection());
+ }
+ Connection& connection = connections.editValueFor(streamId);
+ if (connection.mSurface == surface) {
+ // Nothing to do
+ return NO_ERROR;
+ }
+ // Clear the surface in the connection.
+ if (connection.mSurface != NULL) {
+ if (connection.mStreamType == TV_STREAM_TYPE_INDEPENDENT_VIDEO_SOURCE) {
+ if (Surface::isValid(connection.mSurface)) {
+ connection.mSurface->setSidebandStream(NULL);
+ }
+ }
+ connection.mSurface.clear();
+ }
+ if (connection.mSourceHandle == NULL && connection.mThread == NULL) {
+ // Need to configure stream
+ ::ndk::ScopedAStatus status;
+ std::vector<AidlTvStreamConfig> list;
+ status = mTvInput->getStreamConfigurations(deviceId, &list);
+ if (!status.isOk()) {
+ ALOGE("Couldn't get stream configs for device id:%d result:%d", deviceId,
+ status.getServiceSpecificError());
+ return UNKNOWN_ERROR;
+ }
+ int configIndex = -1;
+ for (size_t i = 0; i < list.size(); ++i) {
+ if (list[i].streamId == streamId) {
+ configIndex = i;
+ break;
+ }
+ }
+ if (configIndex == -1) {
+ ALOGE("Cannot find a config with given stream ID: %d", streamId);
+ return BAD_VALUE;
+ }
+ connection.mStreamType = TV_STREAM_TYPE_INDEPENDENT_VIDEO_SOURCE;
+
+ AidlNativeHandle sidebandStream;
+ status = mTvInput->openStream(deviceId, streamId, &sidebandStream);
+ if (!status.isOk()) {
+ ALOGE("Couldn't open stream. device id:%d stream id:%d result:%d", deviceId, streamId,
+ status.getServiceSpecificError());
+ return UNKNOWN_ERROR;
+ }
+ connection.mSourceHandle = NativeHandle::create(makeFromAidl(sidebandStream), true);
+ }
+ connection.mSurface = surface;
+ if (connection.mSurface != nullptr) {
+ connection.mSurface->setSidebandStream(connection.mSourceHandle);
+ }
+ return NO_ERROR;
+}
+
+int JTvInputHal::removeStream(int deviceId, int streamId) {
+ Mutex::Autolock autoLock(&mStreamLock);
+ KeyedVector<int, Connection>& connections = mConnections.editValueFor(deviceId);
+ if (connections.indexOfKey(streamId) < 0) {
+ return BAD_VALUE;
+ }
+ Connection& connection = connections.editValueFor(streamId);
+ if (connection.mSurface == NULL) {
+ // Nothing to do
+ return NO_ERROR;
+ }
+ if (Surface::isValid(connection.mSurface)) {
+ connection.mSurface->setSidebandStream(NULL);
+ }
+ connection.mSurface.clear();
+ if (connection.mThread != NULL) {
+ connection.mThread->shutdown();
+ connection.mThread.clear();
+ }
+ if (!mTvInput->closeStream(deviceId, streamId).isOk()) {
+ ALOGE("Couldn't close stream. device id:%d stream id:%d", deviceId, streamId);
+ return BAD_VALUE;
+ }
+ if (connection.mSourceHandle != NULL) {
+ connection.mSourceHandle.clear();
+ }
+ return NO_ERROR;
+}
+
+const std::vector<AidlTvStreamConfig> JTvInputHal::getStreamConfigs(int deviceId) {
+ std::vector<AidlTvStreamConfig> list;
+ ::ndk::ScopedAStatus status = mTvInput->getStreamConfigurations(deviceId, &list);
+ if (!status.isOk()) {
+ ALOGE("Couldn't get stream configs for device id:%d result:%d", deviceId,
+ status.getServiceSpecificError());
+ return std::vector<AidlTvStreamConfig>();
+ }
+ return list;
+}
+
+void JTvInputHal::onDeviceAvailable(const TvInputDeviceInfoWrapper& info) {
+ {
+ Mutex::Autolock autoLock(&mLock);
+ mConnections.add(info.deviceId, KeyedVector<int, Connection>());
+ }
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+ jobject builder = env->NewObject(gTvInputHardwareInfoBuilderClassInfo.clazz,
+ gTvInputHardwareInfoBuilderClassInfo.constructor);
+ env->CallObjectMethod(builder, gTvInputHardwareInfoBuilderClassInfo.deviceId, info.deviceId);
+ env->CallObjectMethod(builder, gTvInputHardwareInfoBuilderClassInfo.type, info.type);
+ if (info.type == TvInputType::HDMI) {
+ env->CallObjectMethod(builder, gTvInputHardwareInfoBuilderClassInfo.hdmiPortId,
+ info.portId);
+ }
+ env->CallObjectMethod(builder, gTvInputHardwareInfoBuilderClassInfo.cableConnectionStatus,
+ info.cableConnectionStatus);
+ if (info.isHidl) {
+ hidlSetUpAudioInfo(env, builder, info);
+ } else {
+ AidlAudioDeviceType audioType = info.aidlAudioDevice.type.type;
+ env->CallObjectMethod(builder, gTvInputHardwareInfoBuilderClassInfo.audioType, audioType);
+ if (audioType != AidlAudioDeviceType::NONE) {
+ std::stringstream ss;
+ switch (info.aidlAudioDevice.address.getTag()) {
+ case AidlAudioDeviceAddress::id:
+ ss << info.aidlAudioDevice.address.get<AidlAudioDeviceAddress::id>();
+ break;
+ case AidlAudioDeviceAddress::mac: {
+ std::vector<uint8_t> addrList =
+ info.aidlAudioDevice.address.get<AidlAudioDeviceAddress::mac>();
+ for (int i = 0; i < addrList.size(); i++) {
+ if (i != 0) {
+ ss << ':';
+ }
+ ss << std::uppercase << std::setfill('0') << std::setw(2) << std::hex
+ << static_cast<int32_t>(addrList[i]);
+ }
+ } break;
+ case AidlAudioDeviceAddress::ipv4: {
+ std::vector<uint8_t> addrList =
+ info.aidlAudioDevice.address.get<AidlAudioDeviceAddress::ipv4>();
+ for (int i = 0; i < addrList.size(); i++) {
+ if (i != 0) {
+ ss << '.';
+ }
+ ss << static_cast<int32_t>(addrList[i]);
+ }
+ } break;
+ case AidlAudioDeviceAddress::ipv6: {
+ std::vector<int32_t> addrList =
+ info.aidlAudioDevice.address.get<AidlAudioDeviceAddress::ipv6>();
+ for (int i = 0; i < addrList.size(); i++) {
+ if (i != 0) {
+ ss << ':';
+ }
+ ss << std::uppercase << std::setfill('0') << std::setw(4) << std::hex
+ << addrList[i];
+ }
+ } break;
+ case AidlAudioDeviceAddress::alsa: {
+ std::vector<int32_t> addrList =
+ info.aidlAudioDevice.address.get<AidlAudioDeviceAddress::alsa>();
+ ss << "card=" << addrList[0] << ";device=" << addrList[1];
+ } break;
+ }
+ std::string bufferStr = ss.str();
+ jstring audioAddress = env->NewStringUTF(bufferStr.c_str());
+ env->CallObjectMethod(builder, gTvInputHardwareInfoBuilderClassInfo.audioAddress,
+ audioAddress);
+ env->DeleteLocalRef(audioAddress);
+ }
+ }
+
+ jobject infoObject = env->CallObjectMethod(builder, gTvInputHardwareInfoBuilderClassInfo.build);
+
+ env->CallVoidMethod(mThiz, gTvInputHalClassInfo.deviceAvailable, infoObject);
+
+ env->DeleteLocalRef(builder);
+ env->DeleteLocalRef(infoObject);
+}
+
+void JTvInputHal::onDeviceUnavailable(int deviceId) {
+ {
+ Mutex::Autolock autoLock(&mLock);
+ KeyedVector<int, Connection>& connections = mConnections.editValueFor(deviceId);
+ for (size_t i = 0; i < connections.size(); ++i) {
+ removeStream(deviceId, connections.keyAt(i));
+ }
+ connections.clear();
+ mConnections.removeItem(deviceId);
+ }
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+ env->CallVoidMethod(mThiz, gTvInputHalClassInfo.deviceUnavailable, deviceId);
+}
+
+void JTvInputHal::onStreamConfigurationsChanged(int deviceId, int cableConnectionStatus) {
+ {
+ Mutex::Autolock autoLock(&mLock);
+ KeyedVector<int, Connection>& connections = mConnections.editValueFor(deviceId);
+ for (size_t i = 0; i < connections.size(); ++i) {
+ removeStream(deviceId, connections.keyAt(i));
+ }
+ connections.clear();
+ }
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+ env->CallVoidMethod(mThiz, gTvInputHalClassInfo.streamConfigsChanged, deviceId,
+ cableConnectionStatus);
+}
+
+void JTvInputHal::onCaptured(int deviceId, int streamId, uint32_t seq, bool succeeded) {
+ sp<BufferProducerThread> thread;
+ {
+ Mutex::Autolock autoLock(&mLock);
+ KeyedVector<int, Connection>& connections = mConnections.editValueFor(deviceId);
+ Connection& connection = connections.editValueFor(streamId);
+ if (connection.mThread == NULL) {
+ ALOGE("capture thread not existing.");
+ return;
+ }
+ thread = connection.mThread;
+ }
+ thread->onCaptured(seq, succeeded);
+ if (seq == 0) {
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+ env->CallVoidMethod(mThiz, gTvInputHalClassInfo.firstFrameCaptured, deviceId, streamId);
+ }
+}
+
+JTvInputHal::TvInputDeviceInfoWrapper
+JTvInputHal::TvInputDeviceInfoWrapper::createDeviceInfoWrapper(
+ const AidlTvInputDeviceInfo& aidlTvInputDeviceInfo) {
+ TvInputDeviceInfoWrapper deviceInfo;
+ deviceInfo.isHidl = false;
+ deviceInfo.deviceId = aidlTvInputDeviceInfo.deviceId;
+ deviceInfo.type = aidlTvInputDeviceInfo.type;
+ deviceInfo.portId = aidlTvInputDeviceInfo.portId;
+ deviceInfo.cableConnectionStatus = aidlTvInputDeviceInfo.cableConnectionStatus;
+ deviceInfo.aidlAudioDevice = aidlTvInputDeviceInfo.audioDevice;
+ return deviceInfo;
+}
+
+JTvInputHal::TvInputEventWrapper JTvInputHal::TvInputEventWrapper::createEventWrapper(
+ const AidlTvInputEvent& aidlTvInputEvent) {
+ TvInputEventWrapper event;
+ event.type = aidlTvInputEvent.type;
+ event.deviceInfo =
+ TvInputDeviceInfoWrapper::createDeviceInfoWrapper(aidlTvInputEvent.deviceInfo);
+ return event;
+}
+
+JTvInputHal::NotifyHandler::NotifyHandler(JTvInputHal* hal, const TvInputEventWrapper& event) {
+ mHal = hal;
+ mEvent = event;
+}
+
+void JTvInputHal::NotifyHandler::handleMessage(const Message& message) {
+ switch (mEvent.type) {
+ case TvInputEventType::DEVICE_AVAILABLE: {
+ mHal->onDeviceAvailable(mEvent.deviceInfo);
+ } break;
+ case TvInputEventType::DEVICE_UNAVAILABLE: {
+ mHal->onDeviceUnavailable(mEvent.deviceInfo.deviceId);
+ } break;
+ case TvInputEventType::STREAM_CONFIGURATIONS_CHANGED: {
+ int cableConnectionStatus = static_cast<int>(mEvent.deviceInfo.cableConnectionStatus);
+ mHal->onStreamConfigurationsChanged(mEvent.deviceInfo.deviceId, cableConnectionStatus);
+ } break;
+ default:
+ ALOGE("Unrecognizable event");
+ }
+}
+
+JTvInputHal::TvInputCallback::TvInputCallback(JTvInputHal* hal) {
+ mHal = hal;
+}
+
+::ndk::ScopedAStatus JTvInputHal::TvInputCallback::notify(const AidlTvInputEvent& event) {
+ mHal->mLooper->sendMessage(new NotifyHandler(mHal,
+ TvInputEventWrapper::createEventWrapper(event)),
+ static_cast<int>(event.type));
+ return ::ndk::ScopedAStatus::ok();
+}
+
+JTvInputHal::ITvInputWrapper::ITvInputWrapper(std::shared_ptr<AidlITvInput>& aidlTvInput)
+ : mIsHidl(false), mAidlTvInput(aidlTvInput) {}
+
+::ndk::ScopedAStatus JTvInputHal::ITvInputWrapper::setCallback(
+ const std::shared_ptr<TvInputCallback>& in_callback) {
+ if (mIsHidl) {
+ return hidlSetCallback(in_callback);
+ } else {
+ return mAidlTvInput->setCallback(in_callback);
+ }
+}
+
+::ndk::ScopedAStatus JTvInputHal::ITvInputWrapper::getStreamConfigurations(
+ int32_t in_deviceId, std::vector<AidlTvStreamConfig>* _aidl_return) {
+ if (mIsHidl) {
+ return hidlGetStreamConfigurations(in_deviceId, _aidl_return);
+ } else {
+ return mAidlTvInput->getStreamConfigurations(in_deviceId, _aidl_return);
+ }
+}
+
+::ndk::ScopedAStatus JTvInputHal::ITvInputWrapper::openStream(int32_t in_deviceId,
+ int32_t in_streamId,
+ AidlNativeHandle* _aidl_return) {
+ if (mIsHidl) {
+ return hidlOpenStream(in_deviceId, in_streamId, _aidl_return);
+ } else {
+ return mAidlTvInput->openStream(in_deviceId, in_streamId, _aidl_return);
+ }
+}
+
+::ndk::ScopedAStatus JTvInputHal::ITvInputWrapper::closeStream(int32_t in_deviceId,
+ int32_t in_streamId) {
+ if (mIsHidl) {
+ return hidlCloseStream(in_deviceId, in_streamId);
+ } else {
+ return mAidlTvInput->closeStream(in_deviceId, in_streamId);
+ }
+}
+
+} // namespace android
diff --git a/services/core/jni/tvinput/JTvInputHal.h b/services/core/jni/tvinput/JTvInputHal.h
new file mode 100644
index 000000000000..269729491ecb
--- /dev/null
+++ b/services/core/jni/tvinput/JTvInputHal.h
@@ -0,0 +1,185 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#define TV_INPUT_AIDL_SERVICE_NAME "android.hardware.tv.input.ITvInput/default"
+
+#include <aidl/android/hardware/tv/input/BnTvInputCallback.h>
+#include <aidl/android/hardware/tv/input/CableConnectionStatus.h>
+#include <aidl/android/hardware/tv/input/ITvInput.h>
+#include <aidl/android/media/audio/common/AudioDevice.h>
+#include <aidlcommonsupport/NativeHandle.h>
+#include <android/binder_manager.h>
+#include <nativehelper/JNIHelp.h>
+#include <utils/Errors.h>
+#include <utils/KeyedVector.h>
+#include <utils/Log.h>
+#include <utils/Looper.h>
+#include <utils/NativeHandle.h>
+
+#include <iomanip>
+
+#include "BufferProducerThread.h"
+#include "TvInputHal_hidl.h"
+#include "android_os_MessageQueue.h"
+#include "android_runtime/AndroidRuntime.h"
+#include "android_runtime/android_view_Surface.h"
+#include "tvinput/jstruct.h"
+
+using ::aidl::android::hardware::tv::input::BnTvInputCallback;
+using ::aidl::android::hardware::tv::input::CableConnectionStatus;
+using ::aidl::android::hardware::tv::input::TvInputEventType;
+using ::aidl::android::hardware::tv::input::TvInputType;
+
+using AidlAudioDevice = ::aidl::android::media::audio::common::AudioDevice;
+using AidlAudioDeviceAddress = ::aidl::android::media::audio::common::AudioDeviceAddress;
+using AidlAudioDeviceType = ::aidl::android::media::audio::common::AudioDeviceType;
+using AidlITvInput = ::aidl::android::hardware::tv::input::ITvInput;
+using AidlNativeHandle = ::aidl::android::hardware::common::NativeHandle;
+using AidlTvInputDeviceInfo = ::aidl::android::hardware::tv::input::TvInputDeviceInfo;
+using AidlTvInputEvent = ::aidl::android::hardware::tv::input::TvInputEvent;
+using AidlTvStreamConfig = ::aidl::android::hardware::tv::input::TvStreamConfig;
+
+extern gTvInputHalClassInfoType gTvInputHalClassInfo;
+extern gTvStreamConfigClassInfoType gTvStreamConfigClassInfo;
+extern gTvStreamConfigBuilderClassInfoType gTvStreamConfigBuilderClassInfo;
+extern gTvInputHardwareInfoBuilderClassInfoType gTvInputHardwareInfoBuilderClassInfo;
+
+namespace android {
+
+class JTvInputHal {
+public:
+ ~JTvInputHal();
+
+ static JTvInputHal* createInstance(JNIEnv* env, jobject thiz, const sp<Looper>& looper);
+
+ int addOrUpdateStream(int deviceId, int streamId, const sp<Surface>& surface);
+ int removeStream(int deviceId, int streamId);
+ const std::vector<AidlTvStreamConfig> getStreamConfigs(int deviceId);
+
+private:
+ // Connection between a surface and a stream.
+ class Connection {
+ public:
+ Connection() {}
+
+ sp<Surface> mSurface;
+ tv_stream_type_t mStreamType;
+
+ // Only valid when mStreamType == TV_STREAM_TYPE_INDEPENDENT_VIDEO_SOURCE
+ sp<NativeHandle> mSourceHandle;
+ // Only valid when mStreamType == TV_STREAM_TYPE_BUFFER_PRODUCER
+ sp<BufferProducerThread> mThread;
+ };
+
+ class TvInputDeviceInfoWrapper {
+ public:
+ TvInputDeviceInfoWrapper() {}
+
+ static TvInputDeviceInfoWrapper createDeviceInfoWrapper(
+ const AidlTvInputDeviceInfo& aidlTvInputDeviceInfo);
+ static TvInputDeviceInfoWrapper createDeviceInfoWrapper(
+ const HidlTvInputDeviceInfo& hidlTvInputDeviceInfo);
+
+ bool isHidl;
+ int deviceId;
+ TvInputType type;
+ int portId;
+ CableConnectionStatus cableConnectionStatus;
+ AidlAudioDevice aidlAudioDevice;
+ HidlAudioDevice hidlAudioType;
+ ::android::hardware::hidl_array<uint8_t, 32> hidlAudioAddress;
+ };
+
+ class TvInputEventWrapper {
+ public:
+ TvInputEventWrapper() {}
+
+ static TvInputEventWrapper createEventWrapper(const AidlTvInputEvent& aidlTvInputEvent);
+ static TvInputEventWrapper createEventWrapper(const HidlTvInputEvent& hidlTvInputEvent);
+
+ TvInputEventType type;
+ TvInputDeviceInfoWrapper deviceInfo;
+ };
+
+ class NotifyHandler : public MessageHandler {
+ public:
+ NotifyHandler(JTvInputHal* hal, const TvInputEventWrapper& event);
+
+ void handleMessage(const Message& message) override;
+
+ private:
+ TvInputEventWrapper mEvent;
+ JTvInputHal* mHal;
+ };
+
+ class TvInputCallback : public HidlITvInputCallback, public BnTvInputCallback {
+ public:
+ explicit TvInputCallback(JTvInputHal* hal);
+ ::ndk::ScopedAStatus notify(const AidlTvInputEvent& event) override;
+ Return<void> notify(const HidlTvInputEvent& event) override;
+
+ private:
+ JTvInputHal* mHal;
+ };
+
+ class ITvInputWrapper {
+ public:
+ ITvInputWrapper(std::shared_ptr<AidlITvInput>& aidlTvInput);
+ ITvInputWrapper(sp<HidlITvInput>& hidlTvInput);
+
+ ::ndk::ScopedAStatus setCallback(const std::shared_ptr<TvInputCallback>& in_callback);
+ ::ndk::ScopedAStatus getStreamConfigurations(int32_t in_deviceId,
+ std::vector<AidlTvStreamConfig>* _aidl_return);
+ ::ndk::ScopedAStatus openStream(int32_t in_deviceId, int32_t in_streamId,
+ AidlNativeHandle* _aidl_return);
+ ::ndk::ScopedAStatus closeStream(int32_t in_deviceId, int32_t in_streamId);
+
+ private:
+ ::ndk::ScopedAStatus hidlSetCallback(const std::shared_ptr<TvInputCallback>& in_callback);
+ ::ndk::ScopedAStatus hidlGetStreamConfigurations(
+ int32_t in_deviceId, std::vector<AidlTvStreamConfig>* _aidl_return);
+ ::ndk::ScopedAStatus hidlOpenStream(int32_t in_deviceId, int32_t in_streamId,
+ AidlNativeHandle* _aidl_return);
+ ::ndk::ScopedAStatus hidlCloseStream(int32_t in_deviceId, int32_t in_streamId);
+
+ bool mIsHidl;
+ sp<HidlITvInput> mHidlTvInput;
+ std::shared_ptr<AidlITvInput> mAidlTvInput;
+ };
+
+ JTvInputHal(JNIEnv* env, jobject thiz, std::shared_ptr<ITvInputWrapper> tvInput,
+ const sp<Looper>& looper);
+
+ void hidlSetUpAudioInfo(JNIEnv* env, jobject& builder, const TvInputDeviceInfoWrapper& info);
+ void onDeviceAvailable(const TvInputDeviceInfoWrapper& info);
+ void onDeviceUnavailable(int deviceId);
+ void onStreamConfigurationsChanged(int deviceId, int cableConnectionStatus);
+ void onCaptured(int deviceId, int streamId, uint32_t seq, bool succeeded);
+
+ Mutex mLock;
+ Mutex mStreamLock;
+ jweak mThiz;
+ sp<Looper> mLooper;
+
+ KeyedVector<int, KeyedVector<int, Connection> > mConnections;
+
+ std::shared_ptr<ITvInputWrapper> mTvInput;
+ std::shared_ptr<TvInputCallback> mTvInputCallback;
+};
+
+} // namespace android
diff --git a/services/core/jni/tvinput/OWNERS b/services/core/jni/tvinput/OWNERS
new file mode 100644
index 000000000000..adc5827929dc
--- /dev/null
+++ b/services/core/jni/tvinput/OWNERS
@@ -0,0 +1,3 @@
+include /media/java/android/media/tv/OWNERS
+
+yixiaoluo@google.com
diff --git a/services/core/jni/tvinput/TvInputHal_hidl.cpp b/services/core/jni/tvinput/TvInputHal_hidl.cpp
new file mode 100644
index 000000000000..37cf8445920f
--- /dev/null
+++ b/services/core/jni/tvinput/TvInputHal_hidl.cpp
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "JTvInputHal.h"
+
+// Implement all HIDL related functions here.
+
+namespace android {
+
+void JTvInputHal::hidlSetUpAudioInfo(JNIEnv* env, jobject& builder,
+ const TvInputDeviceInfoWrapper& info) {
+ env->CallObjectMethod(builder, gTvInputHardwareInfoBuilderClassInfo.audioType,
+ info.hidlAudioType);
+ if (info.hidlAudioType != HidlAudioDevice::NONE) {
+ uint8_t buffer[info.hidlAudioAddress.size() + 1];
+ memcpy(buffer, info.hidlAudioAddress.data(), info.hidlAudioAddress.size());
+ buffer[info.hidlAudioAddress.size()] = '\0';
+ jstring audioAddress = env->NewStringUTF(reinterpret_cast<const char*>(buffer));
+ env->CallObjectMethod(builder, gTvInputHardwareInfoBuilderClassInfo.audioAddress,
+ audioAddress);
+ env->DeleteLocalRef(audioAddress);
+ }
+}
+
+JTvInputHal::TvInputDeviceInfoWrapper
+JTvInputHal::TvInputDeviceInfoWrapper::createDeviceInfoWrapper(
+ const HidlTvInputDeviceInfo& hidlTvInputDeviceInfo) {
+ TvInputDeviceInfoWrapper deviceInfo;
+ deviceInfo.isHidl = true;
+ deviceInfo.deviceId = hidlTvInputDeviceInfo.deviceId;
+ deviceInfo.type = TvInputType(static_cast<int32_t>(hidlTvInputDeviceInfo.type));
+ deviceInfo.portId = hidlTvInputDeviceInfo.portId;
+ deviceInfo.cableConnectionStatus = CableConnectionStatus(
+ static_cast<int32_t>(hidlTvInputDeviceInfo.cableConnectionStatus));
+ deviceInfo.hidlAudioType = hidlTvInputDeviceInfo.audioType;
+ deviceInfo.hidlAudioAddress = hidlTvInputDeviceInfo.audioAddress;
+ return deviceInfo;
+}
+
+JTvInputHal::TvInputEventWrapper JTvInputHal::TvInputEventWrapper::createEventWrapper(
+ const HidlTvInputEvent& hidlTvInputEvent) {
+ TvInputEventWrapper event;
+ event.type = TvInputEventType(static_cast<int32_t>(hidlTvInputEvent.type));
+ event.deviceInfo =
+ TvInputDeviceInfoWrapper::createDeviceInfoWrapper(hidlTvInputEvent.deviceInfo);
+ return event;
+}
+
+Return<void> JTvInputHal::TvInputCallback::notify(const HidlTvInputEvent& event) {
+ mHal->mLooper->sendMessage(new NotifyHandler(mHal,
+ TvInputEventWrapper::createEventWrapper(event)),
+ static_cast<int>(event.type));
+ return Void();
+}
+
+JTvInputHal::ITvInputWrapper::ITvInputWrapper(sp<HidlITvInput>& hidlTvInput)
+ : mIsHidl(true), mHidlTvInput(hidlTvInput) {}
+
+::ndk::ScopedAStatus JTvInputHal::ITvInputWrapper::hidlSetCallback(
+ const std::shared_ptr<TvInputCallback>& in_callback) {
+ mHidlTvInput->setCallback(in_callback == nullptr ? nullptr
+ : sp<TvInputCallback>(in_callback.get()));
+ return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus JTvInputHal::ITvInputWrapper::hidlGetStreamConfigurations(
+ int32_t in_deviceId, std::vector<AidlTvStreamConfig>* _aidl_return) {
+ Result result = Result::UNKNOWN;
+ hidl_vec<HidlTvStreamConfig> list;
+ mHidlTvInput->getStreamConfigurations(in_deviceId,
+ [&result, &list](Result res,
+ hidl_vec<HidlTvStreamConfig> configs) {
+ result = res;
+ if (res == Result::OK) {
+ list = configs;
+ }
+ });
+ if (result != Result::OK) {
+ ALOGE("Couldn't get stream configs for device id:%d result:%d", in_deviceId, result);
+ return ::ndk::ScopedAStatus::fromServiceSpecificError(static_cast<int32_t>(result));
+ }
+ for (size_t i = 0; i < list.size(); ++i) {
+ AidlTvStreamConfig config;
+ config.streamId = list[i].streamId;
+ config.maxVideoHeight = list[i].maxVideoHeight;
+ config.maxVideoWidth = list[i].maxVideoWidth;
+ _aidl_return->push_back(config);
+ }
+ return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus JTvInputHal::ITvInputWrapper::hidlOpenStream(int32_t in_deviceId,
+ int32_t in_streamId,
+ AidlNativeHandle* _aidl_return) {
+ Result result = Result::UNKNOWN;
+ native_handle_t* sidebandStream;
+ mHidlTvInput->openStream(in_deviceId, in_streamId,
+ [&result, &sidebandStream](Result res, const native_handle_t* handle) {
+ result = res;
+ if (res == Result::OK) {
+ if (handle) {
+ sidebandStream = native_handle_clone(handle);
+ }
+ }
+ });
+ if (result != Result::OK) {
+ ALOGE("Couldn't open stream. device id:%d stream id:%d result:%d", in_deviceId, in_streamId,
+ result);
+ return ::ndk::ScopedAStatus::fromServiceSpecificError(static_cast<int32_t>(result));
+ }
+ *_aidl_return = makeToAidl(sidebandStream);
+ native_handle_delete(sidebandStream);
+ return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus JTvInputHal::ITvInputWrapper::hidlCloseStream(int32_t in_deviceId,
+ int32_t in_streamId) {
+ Result result = mHidlTvInput->closeStream(in_deviceId, in_streamId);
+ if (result != Result::OK) {
+ return ::ndk::ScopedAStatus::fromServiceSpecificError(static_cast<int32_t>(result));
+ }
+ return ::ndk::ScopedAStatus::ok();
+}
+
+} // namespace android
diff --git a/services/core/jni/tvinput/TvInputHal_hidl.h b/services/core/jni/tvinput/TvInputHal_hidl.h
new file mode 100644
index 000000000000..948f86ba679c
--- /dev/null
+++ b/services/core/jni/tvinput/TvInputHal_hidl.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+// Include all HIDL related files/names here.
+
+#include <android/hardware/tv/input/1.0/ITvInput.h>
+#include <android/hardware/tv/input/1.0/ITvInputCallback.h>
+#include <android/hardware/tv/input/1.0/types.h>
+
+using ::android::hardware::hidl_vec;
+using ::android::hardware::Return;
+using ::android::hardware::Void;
+using ::android::hardware::tv::input::V1_0::Result;
+
+using HidlAudioDevice = ::android::hardware::audio::common::V2_0::AudioDevice;
+using HidlITvInput = ::android::hardware::tv::input::V1_0::ITvInput;
+using HidlITvInputCallback = ::android::hardware::tv::input::V1_0::ITvInputCallback;
+using HidlTvInputDeviceInfo = ::android::hardware::tv::input::V1_0::TvInputDeviceInfo;
+using HidlTvInputEvent = ::android::hardware::tv::input::V1_0::TvInputEvent;
+using HidlTvStreamConfig = ::android::hardware::tv::input::V1_0::TvStreamConfig;
diff --git a/services/core/jni/tvinput/jstruct.h b/services/core/jni/tvinput/jstruct.h
new file mode 100644
index 000000000000..0a4a64dbb40c
--- /dev/null
+++ b/services/core/jni/tvinput/jstruct.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "jni.h"
+
+typedef struct {
+ jmethodID deviceAvailable;
+ jmethodID deviceUnavailable;
+ jmethodID streamConfigsChanged;
+ jmethodID firstFrameCaptured;
+} gTvInputHalClassInfoType;
+
+typedef struct {
+ jclass clazz;
+} gTvStreamConfigClassInfoType;
+
+typedef struct {
+ jclass clazz;
+
+ jmethodID constructor;
+ jmethodID streamId;
+ jmethodID type;
+ jmethodID maxWidth;
+ jmethodID maxHeight;
+ jmethodID generation;
+ jmethodID build;
+} gTvStreamConfigBuilderClassInfoType;
+
+typedef struct {
+ jclass clazz;
+
+ jmethodID constructor;
+ jmethodID deviceId;
+ jmethodID type;
+ jmethodID hdmiPortId;
+ jmethodID cableConnectionStatus;
+ jmethodID audioType;
+ jmethodID audioAddress;
+ jmethodID build;
+} gTvInputHardwareInfoBuilderClassInfoType;
diff --git a/services/core/xsd/display-layout-config/display-layout-config.xsd b/services/core/xsd/display-layout-config/display-layout-config.xsd
index e14139a0860a..842d97acf1d4 100644
--- a/services/core/xsd/display-layout-config/display-layout-config.xsd
+++ b/services/core/xsd/display-layout-config/display-layout-config.xsd
@@ -50,6 +50,7 @@
<xs:complexType name="display">
<xs:sequence>
<xs:element name="address" type="xs:nonNegativeInteger"/>
+ <xs:element name="position" type="xs:string" minOccurs="0" maxOccurs="1" />
</xs:sequence>
<xs:attribute name="enabled" type="xs:boolean" use="optional" />
<xs:attribute name="defaultDisplay" type="xs:boolean" use="optional" />
diff --git a/services/core/xsd/display-layout-config/schema/current.txt b/services/core/xsd/display-layout-config/schema/current.txt
index f3915754a1a4..55f866cbc478 100644
--- a/services/core/xsd/display-layout-config/schema/current.txt
+++ b/services/core/xsd/display-layout-config/schema/current.txt
@@ -4,11 +4,13 @@ package com.android.server.display.config.layout {
public class Display {
ctor public Display();
method public java.math.BigInteger getAddress();
+ method public String getPosition();
method public boolean isDefaultDisplay();
method public boolean isEnabled();
method public void setAddress(java.math.BigInteger);
method public void setDefaultDisplay(boolean);
method public void setEnabled(boolean);
+ method public void setPosition(String);
}
public class Layout {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 775e3d833403..d91f63329af3 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -718,6 +718,16 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
private static final String ENABLE_COEXISTENCE_FLAG = "enable_coexistence";
private static final boolean DEFAULT_ENABLE_COEXISTENCE_FLAG = false;
+ // TODO(b/258425381) remove the flag after rollout.
+ private static final String KEEP_PROFILES_RUNNING_FLAG = "enable_keep_profiles_running";
+ private static final boolean DEFAULT_KEEP_PROFILES_RUNNING_FLAG = false;
+
+ /**
+ * This feature flag is checked once after boot and this value us used until the next reboot to
+ * avoid needing to handle the flag changing on the fly.
+ */
+ private final boolean mKeepProfilesRunning = isKeepProfilesRunningFlagEnabled();
+
/**
* For apps targeting U+
* Enable multiple admins to coexist on the same device.
@@ -1935,10 +1945,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
private Owners makeOwners(Injector injector, PolicyPathProvider pathProvider) {
- return new Owners(injector.getUserManager(), injector.getUserManagerInternal(),
+ return new Owners(
+ injector.getUserManager(), injector.getUserManagerInternal(),
injector.getPackageManagerInternal(),
injector.getActivityTaskManagerInternal(),
- injector.getActivityManagerInternal(), pathProvider);
+ injector.getActivityManagerInternal(), mStateCache, pathProvider);
}
/**
@@ -9892,6 +9903,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
(size == 1 ? "" : "s"));
}
pw.println();
+ pw.println("Keep profiles running: " + mKeepProfilesRunning);
+ pw.println();
mPolicyCache.dump(pw);
pw.println();
@@ -13240,7 +13253,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
CallerIdentity caller = new CallerIdentity(callerUid, null, null);
if (isUserAffiliatedWithDevice(UserHandle.getUserId(callerUid))
&& (isActiveProfileOwner(callerUid)
- || isDefaultDeviceOwner(caller) || isFinancedDeviceOwner(caller))) {
+ || isDefaultDeviceOwner(caller) || isFinancedDeviceOwner(caller))) {
// device owner or a profile owner affiliated with the device owner
return true;
}
@@ -13533,11 +13546,21 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
}
+ @Override
+ public boolean isKeepProfilesRunningEnabled() {
+ return mKeepProfilesRunning;
+ }
+
private @Mode int findInteractAcrossProfilesResetMode(String packageName) {
return getDefaultCrossProfilePackages().contains(packageName)
? AppOpsManager.MODE_ALLOWED
: AppOpsManager.opToDefaultMode(AppOpsManager.OP_INTERACT_ACROSS_PROFILES);
}
+
+ @Override
+ public boolean isUserOrganizationManaged(@UserIdInt int userHandle) {
+ return getDeviceStateCache().isUserOrganizationManaged(userHandle);
+ }
}
private Intent createShowAdminSupportIntent(int userId) {
@@ -14387,7 +14410,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
// Bail out if we are trying to provision a work profile but one already exists.
if (!mUserManager.canAddMoreManagedProfiles(
callingUserId, /* allowedToRemoveOne= */ false)) {
- Slogf.i(LOG_TAG, "A work profile already exists.");
+ Slogf.i(LOG_TAG, "Cannot add more managed profiles.");
return STATUS_CANNOT_ADD_MANAGED_PROFILE;
}
} finally {
@@ -19167,4 +19190,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
ENABLE_COEXISTENCE_FLAG,
DEFAULT_ENABLE_COEXISTENCE_FLAG);
}
+
+ private static boolean isKeepProfilesRunningFlagEnabled() {
+ return DeviceConfig.getBoolean(
+ NAMESPACE_DEVICE_POLICY_MANAGER,
+ KEEP_PROFILES_RUNNING_FLAG,
+ DEFAULT_KEEP_PROFILES_RUNNING_FLAG);
+ }
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DeviceStateCacheImpl.java b/services/devicepolicy/java/com/android/server/devicepolicy/DeviceStateCacheImpl.java
index 1215253dacaf..011a282ead7a 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DeviceStateCacheImpl.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DeviceStateCacheImpl.java
@@ -15,11 +15,17 @@
*/
package com.android.server.devicepolicy;
+import android.annotation.UserIdInt;
+import android.app.admin.DevicePolicyManager;
import android.app.admin.DeviceStateCache;
import android.util.IndentingPrintWriter;
import com.android.internal.annotations.GuardedBy;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
+
/**
* Implementation of {@link DeviceStateCache}, to which {@link DevicePolicyManagerService} pushes
* device state.
@@ -32,6 +38,11 @@ public class DeviceStateCacheImpl extends DeviceStateCache {
*/
private final Object mLock = new Object();
+ public static final int NO_DEVICE_OWNER = -1;
+
+ private AtomicInteger mDeviceOwnerType = new AtomicInteger(NO_DEVICE_OWNER);
+ private Map<Integer, Boolean> mHasProfileOwner = new ConcurrentHashMap<>();
+
@GuardedBy("mLock")
private boolean mIsDeviceProvisioned = false;
@@ -47,11 +58,43 @@ public class DeviceStateCacheImpl extends DeviceStateCache {
}
}
+ void setDeviceOwnerType(int deviceOwnerType) {
+ mDeviceOwnerType.set(deviceOwnerType);
+ }
+
+ void setHasProfileOwner(int userId, boolean hasProfileOwner) {
+ if (hasProfileOwner) {
+ mHasProfileOwner.put(userId, true);
+ } else {
+ mHasProfileOwner.remove(userId);
+ }
+ }
+
+ @Override
+ public boolean isUserOrganizationManaged(@UserIdInt int userHandle) {
+ if (mHasProfileOwner.getOrDefault(userHandle, false)
+ || hasEnterpriseDeviceOwner()) {
+ return true;
+ }
+
+ // TODO: Support role holder override
+ return false;
+ }
+
+ private boolean hasEnterpriseDeviceOwner() {
+ return mDeviceOwnerType.get() == DevicePolicyManager.DEVICE_OWNER_TYPE_DEFAULT;
+ }
+
/** Dump content */
public void dump(IndentingPrintWriter pw) {
pw.println("Device state cache:");
pw.increaseIndent();
pw.println("Device provisioned: " + mIsDeviceProvisioned);
+ pw.println("Device Owner Type: " + mDeviceOwnerType.get());
+ pw.println("Has PO:");
+ for (Integer id : mHasProfileOwner.keySet()) {
+ pw.println("User " + id + ": " + mHasProfileOwner.get(id));
+ }
pw.decreaseIndent();
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
index 3b46d5238e6a..6f172e4515fc 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
@@ -16,8 +16,12 @@
package com.android.server.devicepolicy;
+import static android.app.admin.DevicePolicyManager.DEPRECATE_USERMANAGERINTERNAL_DEVICEPOLICY_DEFAULT;
+import static android.app.admin.DevicePolicyManager.DEPRECATE_USERMANAGERINTERNAL_DEVICEPOLICY_FLAG;
import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_DEFAULT;
+import static com.android.server.devicepolicy.DeviceStateCacheImpl.NO_DEVICE_OWNER;
+
import android.annotation.Nullable;
import android.app.ActivityManagerInternal;
import android.app.AppOpsManagerInternal;
@@ -31,6 +35,7 @@ import android.os.Binder;
import android.os.Process;
import android.os.UserHandle;
import android.os.UserManager;
+import android.provider.DeviceConfig;
import android.util.ArraySet;
import android.util.IndentingPrintWriter;
import android.util.Pair;
@@ -69,6 +74,7 @@ class Owners {
private final PackageManagerInternal mPackageManagerInternal;
private final ActivityTaskManagerInternal mActivityTaskManagerInternal;
private final ActivityManagerInternal mActivityManagerInternal;
+ private final DeviceStateCacheImpl mDeviceStateCache;
@GuardedBy("mData")
private final OwnersData mData;
@@ -81,12 +87,14 @@ class Owners {
PackageManagerInternal packageManagerInternal,
ActivityTaskManagerInternal activityTaskManagerInternal,
ActivityManagerInternal activityManagerInternal,
+ DeviceStateCacheImpl deviceStateCache,
PolicyPathProvider pathProvider) {
mUserManager = userManager;
mUserManagerInternal = userManagerInternal;
mPackageManagerInternal = packageManagerInternal;
mActivityTaskManagerInternal = activityTaskManagerInternal;
mActivityManagerInternal = activityManagerInternal;
+ mDeviceStateCache = deviceStateCache;
mData = new OwnersData(pathProvider);
}
@@ -99,9 +107,24 @@ class Owners {
mUserManager.getAliveUsers().stream().mapToInt(u -> u.id).toArray();
mData.load(usersIds);
- mUserManagerInternal.setDeviceManaged(hasDeviceOwner());
- for (int userId : usersIds) {
- mUserManagerInternal.setUserManaged(userId, hasProfileOwner(userId));
+ // TODO(b/258213147): Remove
+ if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER,
+ DEPRECATE_USERMANAGERINTERNAL_DEVICEPOLICY_FLAG,
+ DEPRECATE_USERMANAGERINTERNAL_DEVICEPOLICY_DEFAULT)) {
+ if (hasDeviceOwner()) {
+ int deviceOwnerType = mData.mDeviceOwnerTypes.getOrDefault(
+ mData.mDeviceOwner.packageName,
+ /* defaultValue= */ DEVICE_OWNER_TYPE_DEFAULT);
+ mDeviceStateCache.setDeviceOwnerType(deviceOwnerType);
+ } else {
+ mDeviceStateCache.setDeviceOwnerType(NO_DEVICE_OWNER);
+ }
+
+ } else {
+ mUserManagerInternal.setDeviceManaged(hasDeviceOwner());
+ for (int userId : usersIds) {
+ mUserManagerInternal.setUserManaged(userId, hasProfileOwner(userId));
+ }
}
notifyChangeLocked();
@@ -224,7 +247,18 @@ class Owners {
/* remoteBugreportHash =*/ null, /* isOrganizationOwnedDevice =*/ true);
mData.mDeviceOwnerUserId = userId;
- mUserManagerInternal.setDeviceManaged(true);
+ // TODO(b/258213147): Remove
+ if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER,
+ DEPRECATE_USERMANAGERINTERNAL_DEVICEPOLICY_FLAG,
+ DEPRECATE_USERMANAGERINTERNAL_DEVICEPOLICY_DEFAULT)) {
+ int deviceOwnerType = mData.mDeviceOwnerTypes.getOrDefault(
+ mData.mDeviceOwner.packageName,
+ /* defaultValue= */ DEVICE_OWNER_TYPE_DEFAULT);
+ mDeviceStateCache.setDeviceOwnerType(deviceOwnerType);
+ } else {
+ mUserManagerInternal.setDeviceManaged(true);
+ }
+
notifyChangeLocked();
pushToActivityTaskManagerLocked();
}
@@ -236,7 +270,14 @@ class Owners {
mData.mDeviceOwner = null;
mData.mDeviceOwnerUserId = UserHandle.USER_NULL;
- mUserManagerInternal.setDeviceManaged(false);
+ // TODO(b/258213147): Remove
+ if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER,
+ DEPRECATE_USERMANAGERINTERNAL_DEVICEPOLICY_FLAG,
+ DEPRECATE_USERMANAGERINTERNAL_DEVICEPOLICY_DEFAULT)) {
+ mDeviceStateCache.setDeviceOwnerType(NO_DEVICE_OWNER);
+ } else {
+ mUserManagerInternal.setDeviceManaged(false);
+ }
notifyChangeLocked();
pushToActivityTaskManagerLocked();
}
@@ -248,7 +289,15 @@ class Owners {
mData.mProfileOwners.put(userId, new OwnerInfo(admin,
/* remoteBugreportUri =*/ null, /* remoteBugreportHash =*/ null,
/* isOrganizationOwnedDevice =*/ false));
- mUserManagerInternal.setUserManaged(userId, true);
+
+ // TODO(b/258213147): Remove
+ if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER,
+ DEPRECATE_USERMANAGERINTERNAL_DEVICEPOLICY_FLAG,
+ DEPRECATE_USERMANAGERINTERNAL_DEVICEPOLICY_DEFAULT)) {
+ mDeviceStateCache.setHasProfileOwner(userId, true);
+ } else {
+ mUserManagerInternal.setUserManaged(userId, true);
+ }
notifyChangeLocked();
}
}
@@ -256,7 +305,14 @@ class Owners {
void removeProfileOwner(int userId) {
synchronized (mData) {
mData.mProfileOwners.remove(userId);
- mUserManagerInternal.setUserManaged(userId, false);
+ // TODO(b/258213147): Remove
+ if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER,
+ DEPRECATE_USERMANAGERINTERNAL_DEVICEPOLICY_FLAG,
+ DEPRECATE_USERMANAGERINTERNAL_DEVICEPOLICY_DEFAULT)) {
+ mDeviceStateCache.setHasProfileOwner(userId, false);
+ } else {
+ mUserManagerInternal.setUserManaged(userId, false);
+ }
notifyChangeLocked();
}
}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index c346b2f52dc2..e41e781b7c22 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -187,6 +187,7 @@ import com.android.server.security.AttestationVerificationManagerService;
import com.android.server.security.FileIntegrityService;
import com.android.server.security.KeyAttestationApplicationIdProviderService;
import com.android.server.security.KeyChainSystemService;
+import com.android.server.security.rkp.RemoteProvisioningService;
import com.android.server.sensorprivacy.SensorPrivacyService;
import com.android.server.sensors.SensorService;
import com.android.server.signedconfig.SignedConfigService;
@@ -211,6 +212,7 @@ import com.android.server.usage.UsageStatsService;
import com.android.server.utils.TimingsTraceAndSlog;
import com.android.server.vibrator.VibratorManagerService;
import com.android.server.vr.VrManagerService;
+import com.android.server.wearable.WearableSensingManagerService;
import com.android.server.webkit.WebViewUpdateService;
import com.android.server.wm.ActivityTaskManagerService;
import com.android.server.wm.WindowManagerGlobalLock;
@@ -1360,11 +1362,16 @@ public final class SystemServer implements Dumpable {
mSystemServiceManager.startService(BugreportManagerService.class);
t.traceEnd();
- // Serivce for GPU and GPU driver.
+ // Service for GPU and GPU driver.
t.traceBegin("GpuService");
mSystemServiceManager.startService(GpuService.class);
t.traceEnd();
+ // Handles system process requests for remotely provisioned keys & data.
+ t.traceBegin("StartRemoteProvisioningService");
+ mSystemServiceManager.startService(RemoteProvisioningService.class);
+ t.traceEnd();
+
t.traceEnd(); // startCoreServices
}
@@ -1830,6 +1837,7 @@ public final class SystemServer implements Dumpable {
startSystemCaptionsManagerService(context, t);
startTextToSpeechManagerService(context, t);
startAmbientContextService(t);
+ startWearableSensingService(t);
// System Speech Recognition Service
t.traceBegin("StartSpeechRecognitionManagerService");
@@ -3170,6 +3178,12 @@ public final class SystemServer implements Dumpable {
t.traceEnd();
}
+ private void startWearableSensingService(@NonNull TimingsTraceAndSlog t) {
+ t.traceBegin("startWearableSensingService");
+ mSystemServiceManager.startService(WearableSensingManagerService.class);
+ t.traceEnd();
+ }
+
private static void startSystemUi(Context context, WindowManagerService windowManager) {
PackageManagerInternal pm = LocalServices.getService(PackageManagerInternal.class);
Intent intent = new Intent();
diff --git a/services/permission/Android.bp b/services/permission/Android.bp
index b03f17b1bef7..dc9b5585cbf2 100644
--- a/services/permission/Android.bp
+++ b/services/permission/Android.bp
@@ -29,6 +29,8 @@ java_library_static {
],
static_libs: [
"kotlin-stdlib",
+ // Adds reflection-less suppressed exceptions and AutoCloseable.use().
+ "kotlin-stdlib-jdk7",
],
jarjar_rules: "jarjar-rules.txt",
kotlincflags: [
diff --git a/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt b/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt
index c6ccc0fb1cff..7b96d42c8abe 100644
--- a/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt
+++ b/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt
@@ -40,46 +40,53 @@ class AccessCheckingService {
}
fun getDecision(subject: AccessUri, `object`: AccessUri): Int =
- policy.getDecision(subject, `object`, state)
+ getState {
+ with(policy) { getDecision(subject, `object`) }
+ }
fun setDecision(subject: AccessUri, `object`: AccessUri, decision: Int) {
- mutateState { oldState, newState ->
- policy.setDecision(subject, `object`, decision, oldState, newState)
+ mutateState {
+ with(policy) { setDecision(subject, `object`, decision) }
}
}
fun onUserAdded(userId: Int) {
- mutateState { oldState, newState ->
- policy.onUserAdded(userId, oldState, newState)
+ mutateState {
+ with(policy) { onUserAdded(userId) }
}
}
fun onUserRemoved(userId: Int) {
- mutateState { oldState, newState ->
- policy.onUserRemoved(userId, oldState, newState)
+ mutateState {
+ with(policy) { onUserRemoved(userId) }
}
}
fun onPackageAdded(packageState: PackageState) {
- mutateState { oldState, newState ->
- policy.onPackageAdded(packageState, oldState, newState)
+ mutateState {
+ with(policy) { onPackageAdded(packageState) }
}
}
fun onPackageRemoved(packageState: PackageState) {
- mutateState { oldState, newState ->
- policy.onPackageRemoved(packageState, oldState, newState)
+ mutateState {
+ with(policy) { onPackageRemoved(packageState) }
}
}
- // TODO: Replace (oldState, newState) with Kotlin context receiver once it's stabilized.
- private inline fun mutateState(action: (oldState: AccessState, newState: AccessState) -> Unit) {
+ internal inline fun <T> getState(action: GetStateScope.() -> T): T =
+ GetStateScope(state).action()
+
+ internal inline fun mutateState(action: MutateStateScope.() -> Unit) {
synchronized(stateLock) {
val oldState = state
val newState = oldState.copy()
- action(oldState, newState)
+ MutateStateScope(oldState, newState).action()
persistence.write(newState)
state = newState
}
}
+
+ internal fun getSchemePolicy(subjectScheme: String, objectScheme: String): SchemePolicy =
+ policy.getSchemePolicy(subjectScheme, objectScheme)
}
diff --git a/services/permission/java/com/android/server/permission/access/AccessPersistence.kt b/services/permission/java/com/android/server/permission/access/AccessPersistence.kt
index 0efc1bd00de2..022f09a028db 100644
--- a/services/permission/java/com/android/server/permission/access/AccessPersistence.kt
+++ b/services/permission/java/com/android/server/permission/access/AccessPersistence.kt
@@ -44,13 +44,13 @@ class AccessPersistence(
systemFile.parse {
// This is the canonical way to call an extension function in a different class.
// TODO(b/259469752): Use context receiver for this when it becomes stable.
- with(policy) { this@parse.parseSystemState(systemState) }
+ with(policy) { parseSystemState(systemState) }
}
}
private fun readUserState(userId: Int, userState: UserState) {
getUserFile(userId).parse {
- with(policy) { this@parse.parseUserState(userId, userState) }
+ with(policy) { parseUserState(userId, userState) }
}
}
@@ -82,13 +82,13 @@ class AccessPersistence(
private fun writeSystemState(systemState: SystemState) {
systemFile.serialize {
- with(policy) { this@serialize.serializeSystemState(systemState) }
+ with(policy) { serializeSystemState(systemState) }
}
}
private fun writeUserState(userId: Int, userState: UserState) {
getUserFile(userId).serialize {
- with(policy) { this@serialize.serializeUserState(userId, userState) }
+ with(policy) { serializeUserState(userId, userState) }
}
}
diff --git a/services/permission/java/com/android/server/permission/access/AccessPolicy.kt b/services/permission/java/com/android/server/permission/access/AccessPolicy.kt
index 4e53ce06f3eb..e9741c69aa5b 100644
--- a/services/permission/java/com/android/server/permission/access/AccessPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/AccessPolicy.kt
@@ -41,38 +41,35 @@ class AccessPolicy private constructor(
}
)
- fun getDecision(subject: AccessUri, `object`: AccessUri, state: AccessState): Int =
- getSchemePolicy(subject, `object`).getDecision(subject, `object`, state)
+ fun getSchemePolicy(subjectScheme: String, objectScheme: String): SchemePolicy =
+ checkNotNull(schemePolicies[subjectScheme]?.get(objectScheme)) {
+ "Scheme policy for $subjectScheme and $objectScheme does not exist"
+ }
- fun setDecision(
- subject: AccessUri,
- `object`: AccessUri,
- decision: Int,
- oldState: AccessState,
- newState: AccessState
- ) {
- getSchemePolicy(subject, `object`)
- .setDecision(subject, `object`, decision, oldState, newState)
- }
+ fun GetStateScope.getDecision(subject: AccessUri, `object`: AccessUri): Int =
+ with(getSchemePolicy(subject, `object`)){ getDecision(subject, `object`) }
- private fun getSchemePolicy(subject: AccessUri, `object`: AccessUri): SchemePolicy =
- checkNotNull(schemePolicies[subject.scheme]?.get(`object`.scheme)) {
- "Scheme policy for subject=$subject object=$`object` does not exist"
- }
+ fun MutateStateScope.setDecision(subject: AccessUri, `object`: AccessUri, decision: Int) {
+ with(getSchemePolicy(subject, `object`)) { setDecision(subject, `object`, decision) }
+ }
- fun onUserAdded(userId: Int, oldState: AccessState, newState: AccessState) {
+ fun MutateStateScope.onUserAdded(userId: Int) {
newState.systemState.userIds += userId
newState.userStates[userId] = UserState()
- forEachSchemePolicy { it.onUserAdded(userId, oldState, newState) }
+ forEachSchemePolicy {
+ with(it) { onUserAdded(userId) }
+ }
}
- fun onUserRemoved(userId: Int, oldState: AccessState, newState: AccessState) {
+ fun MutateStateScope.onUserRemoved(userId: Int) {
newState.systemState.userIds -= userId
newState.userStates -= userId
- forEachSchemePolicy { it.onUserRemoved(userId, oldState, newState) }
+ forEachSchemePolicy {
+ with(it) { onUserRemoved(userId) }
+ }
}
- fun onPackageAdded(packageState: PackageState, oldState: AccessState, newState: AccessState) {
+ fun MutateStateScope.onPackageAdded(packageState: PackageState) {
var isAppIdAdded = false
newState.systemState.apply {
packageStates[packageState.packageName] = packageState
@@ -82,12 +79,16 @@ class AccessPolicy private constructor(
}.add(packageState.packageName)
}
if (isAppIdAdded) {
- forEachSchemePolicy { it.onAppIdAdded(packageState.appId, oldState, newState) }
+ forEachSchemePolicy {
+ with(it) { onAppIdAdded(packageState.appId) }
+ }
+ }
+ forEachSchemePolicy {
+ with(it) { onPackageAdded(packageState) }
}
- forEachSchemePolicy { it.onPackageAdded(packageState, oldState, newState) }
}
- fun onPackageRemoved(packageState: PackageState, oldState: AccessState, newState: AccessState) {
+ fun MutateStateScope.onPackageRemoved(packageState: PackageState) {
var isAppIdRemoved = false
newState.systemState.apply {
packageStates -= packageState.packageName
@@ -101,9 +102,13 @@ class AccessPolicy private constructor(
}
}
}
- forEachSchemePolicy { it.onPackageRemoved(packageState, oldState, newState) }
+ forEachSchemePolicy {
+ with(it) { onPackageRemoved(packageState) }
+ }
if (isAppIdRemoved) {
- forEachSchemePolicy { it.onAppIdRemoved(packageState.appId, oldState, newState) }
+ forEachSchemePolicy {
+ with(it) { onAppIdRemoved(packageState.appId) }
+ }
}
}
@@ -113,7 +118,7 @@ class AccessPolicy private constructor(
TAG_ACCESS -> {
forEachTag {
forEachSchemePolicy {
- with(it) { this@parseSystemState.parseSystemState(systemState) }
+ with(it) { parseSystemState(systemState) }
}
}
}
@@ -125,7 +130,7 @@ class AccessPolicy private constructor(
fun BinaryXmlSerializer.serializeSystemState(systemState: SystemState) {
tag(TAG_ACCESS) {
forEachSchemePolicy {
- with(it) { this@serializeSystemState.serializeSystemState(systemState) }
+ with(it) { serializeSystemState(systemState) }
}
}
}
@@ -136,7 +141,7 @@ class AccessPolicy private constructor(
TAG_ACCESS -> {
forEachTag {
forEachSchemePolicy {
- with(it) { this@parseUserState.parseUserState(userId, userState) }
+ with(it) { parseUserState(userId, userState) }
}
}
}
@@ -153,11 +158,14 @@ class AccessPolicy private constructor(
fun BinaryXmlSerializer.serializeUserState(userId: Int, userState: UserState) {
tag(TAG_ACCESS) {
forEachSchemePolicy {
- with(it) { this@serializeUserState.serializeUserState(userId, userState) }
+ with(it) { serializeUserState(userId, userState) }
}
}
}
+ private fun getSchemePolicy(subject: AccessUri, `object`: AccessUri): SchemePolicy =
+ getSchemePolicy(subject.scheme, `object`.scheme)
+
private inline fun forEachSchemePolicy(action: (SchemePolicy) -> Unit) {
schemePolicies.forEachValueIndexed { _, objectSchemePolicies ->
objectSchemePolicies.forEachValueIndexed { _, schemePolicy ->
@@ -182,14 +190,12 @@ abstract class SchemePolicy {
abstract val objectScheme: String
- abstract fun getDecision(subject: AccessUri, `object`: AccessUri, state: AccessState): Int
+ abstract fun GetStateScope.getDecision(subject: AccessUri, `object`: AccessUri): Int
- abstract fun setDecision(
+ abstract fun MutateStateScope.setDecision(
subject: AccessUri,
`object`: AccessUri,
- decision: Int,
- oldState: AccessState,
- newState: AccessState
+ decision: Int
)
fun addOnDecisionChangedListener(listener: OnDecisionChangedListener) {
@@ -216,25 +222,17 @@ abstract class SchemePolicy {
}
}
- open fun onUserAdded(userId: Int, oldState: AccessState, newState: AccessState) {}
+ open fun MutateStateScope.onUserAdded(userId: Int) {}
- open fun onUserRemoved(userId: Int, oldState: AccessState, newState: AccessState) {}
+ open fun MutateStateScope.onUserRemoved(userId: Int) {}
- open fun onAppIdAdded(appId: Int, oldState: AccessState, newState: AccessState) {}
+ open fun MutateStateScope.onAppIdAdded(appId: Int) {}
- open fun onAppIdRemoved(appId: Int, oldState: AccessState, newState: AccessState) {}
+ open fun MutateStateScope.onAppIdRemoved(appId: Int) {}
- open fun onPackageAdded(
- packageState: PackageState,
- oldState: AccessState,
- newState: AccessState
- ) {}
+ open fun MutateStateScope.onPackageAdded(packageState: PackageState) {}
- open fun onPackageRemoved(
- packageState: PackageState,
- oldState: AccessState,
- newState: AccessState
- ) {}
+ open fun MutateStateScope.onPackageRemoved(packageState: PackageState) {}
open fun BinaryXmlPullParser.parseSystemState(systemState: SystemState) {}
diff --git a/services/permission/java/com/android/server/permission/access/AccessState.kt b/services/permission/java/com/android/server/permission/access/AccessState.kt
index f496dbd03130..cf8f38360cff 100644
--- a/services/permission/java/com/android/server/permission/access/AccessState.kt
+++ b/services/permission/java/com/android/server/permission/access/AccessState.kt
@@ -124,3 +124,12 @@ abstract class WritableState {
}
}
}
+
+class GetStateScope(
+ val state: AccessState
+)
+
+class MutateStateScope(
+ val oldState: AccessState,
+ val newState: AccessState
+)
diff --git a/services/permission/java/com/android/server/permission/access/appop/AppOpsCheckingServiceCompatImpl.kt b/services/permission/java/com/android/server/permission/access/appop/AppOpsCheckingServiceCompatImpl.kt
new file mode 100644
index 000000000000..a565feb959c2
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/appop/AppOpsCheckingServiceCompatImpl.kt
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.permission.access.appop
+
+import android.util.ArraySet
+import android.util.SparseBooleanArray
+import android.util.SparseIntArray
+import com.android.server.appop.AppOpsCheckingServiceInterface
+import com.android.server.appop.OnOpModeChangedListener
+import com.android.server.permission.access.AccessCheckingService
+import java.io.PrintWriter
+
+class AppOpsCheckingServiceCompatImpl(
+ private val accessCheckingService: AccessCheckingService
+) : AppOpsCheckingServiceInterface {
+ override fun getNonDefaultUidModes(uid: Int): SparseIntArray {
+ TODO("Not yet implemented")
+ }
+
+ override fun getUidMode(uid: Int, op: Int): Int {
+ TODO("Not yet implemented")
+ }
+
+ override fun setUidMode(uid: Int, op: Int, mode: Int): Boolean {
+ TODO("Not yet implemented")
+ }
+
+ override fun getPackageMode(packageName: String, op: Int, userId: Int): Int {
+ TODO("Not yet implemented")
+ }
+
+ override fun setPackageMode(packageName: String, op: Int, mode: Int, userId: Int) {
+ TODO("Not yet implemented")
+ }
+
+ override fun removePackage(packageName: String, userId: Int): Boolean {
+ TODO("Not yet implemented")
+ }
+
+ override fun removeUid(uid: Int) {
+ TODO("Not yet implemented")
+ }
+
+ override fun areUidModesDefault(uid: Int): Boolean {
+ TODO("Not yet implemented")
+ }
+
+ override fun arePackageModesDefault(packageName: String, userId: Int): Boolean {
+ TODO("Not yet implemented")
+ }
+
+ override fun clearAllModes() {
+ TODO("Not yet implemented")
+ }
+
+ override fun startWatchingOpModeChanged(changedListener: OnOpModeChangedListener, op: Int) {
+ TODO("Not yet implemented")
+ }
+
+ override fun startWatchingPackageModeChanged(
+ changedListener: OnOpModeChangedListener,
+ packageName: String
+ ) {
+ TODO("Not yet implemented")
+ }
+
+ override fun removeListener(changedListener: OnOpModeChangedListener) {
+ TODO("Not yet implemented")
+ }
+
+ override fun getOpModeChangedListeners(op: Int): ArraySet<OnOpModeChangedListener> {
+ TODO("Not yet implemented")
+ }
+
+ override fun getPackageModeChangedListeners(
+ packageName: String
+ ): ArraySet<OnOpModeChangedListener> {
+ TODO("Not yet implemented")
+ }
+
+ override fun notifyWatchersOfChange(op: Int, uid: Int) {
+ TODO("Not yet implemented")
+ }
+
+ override fun notifyOpChanged(
+ changedListener: OnOpModeChangedListener,
+ op: Int,
+ uid: Int,
+ packageName: String?
+ ) {
+ TODO("Not yet implemented")
+ }
+
+ override fun notifyOpChangedForAllPkgsInUid(
+ op: Int,
+ uid: Int,
+ onlyForeground: Boolean,
+ callbackToIgnore: OnOpModeChangedListener?
+ ) {
+ TODO("Not yet implemented")
+ }
+
+ override fun evalForegroundUidOps(
+ uid: Int,
+ foregroundOps: SparseBooleanArray?
+ ): SparseBooleanArray {
+ TODO("Not yet implemented")
+ }
+
+ override fun evalForegroundPackageOps(
+ packageName: String,
+ foregroundOps: SparseBooleanArray?,
+ userId: Int
+ ): SparseBooleanArray {
+ TODO("Not yet implemented")
+ }
+
+ override fun dumpListeners(
+ dumpOp: Int,
+ dumpUid: Int,
+ dumpPackage: String?,
+ printWriter: PrintWriter
+ ): Boolean {
+ TODO("Not yet implemented")
+ }
+
+ companion object {
+ private val LOG_TAG = AppOpsCheckingServiceCompatImpl::class.java.simpleName
+ }
+}
diff --git a/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPolicy.kt b/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPolicy.kt
index 0b052f918a20..a1a5e2d90091 100644
--- a/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPolicy.kt
@@ -19,44 +19,43 @@ package com.android.server.permission.access.appop
import android.app.AppOpsManager
import com.android.modules.utils.BinaryXmlPullParser
import com.android.modules.utils.BinaryXmlSerializer
-import com.android.server.permission.access.AccessState
import com.android.server.permission.access.AccessUri
import com.android.server.permission.access.AppOpUri
+import com.android.server.permission.access.GetStateScope
+import com.android.server.permission.access.MutateStateScope
import com.android.server.permission.access.SchemePolicy
import com.android.server.permission.access.UserState
import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
abstract class BaseAppOpPolicy(private val persistence: BaseAppOpPersistence) : SchemePolicy() {
- override fun getDecision(subject: AccessUri, `object`: AccessUri, state: AccessState): Int {
+ override fun GetStateScope.getDecision(subject: AccessUri, `object`: AccessUri): Int {
`object` as AppOpUri
- return getModes(subject, state)
+ return getModes(subject)
.getWithDefault(`object`.appOpName, opToDefaultMode(`object`.appOpName))
}
- override fun setDecision(
+ override fun MutateStateScope.setDecision(
subject: AccessUri,
`object`: AccessUri,
- decision: Int,
- oldState: AccessState,
- newState: AccessState
+ decision: Int
) {
`object` as AppOpUri
- val modes = getOrCreateModes(subject, newState)
+ val modes = getOrCreateModes(subject)
val oldMode = modes.putWithDefault(`object`.appOpName, decision,
opToDefaultMode(`object`.appOpName))
if (modes.isEmpty()) {
- removeModes(subject, newState)
+ removeModes(subject)
}
if (oldMode != decision) {
notifyOnDecisionChangedListeners(subject, `object`, oldMode, decision)
}
}
- abstract fun getModes(subject: AccessUri, state: AccessState): IndexedMap<String, Int>?
+ abstract fun GetStateScope.getModes(subject: AccessUri): IndexedMap<String, Int>?
- abstract fun getOrCreateModes(subject: AccessUri, state: AccessState): IndexedMap<String, Int>
+ abstract fun MutateStateScope.getOrCreateModes(subject: AccessUri): IndexedMap<String, Int>
- abstract fun removeModes(subject: AccessUri, state: AccessState)
+ abstract fun MutateStateScope.removeModes(subject: AccessUri)
// TODO need to check that [AppOpsManager.getSystemAlertWindowDefault] works; likely no issue
// since running in system process.
diff --git a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt
index 7c3c14c1d753..966489f72aa2 100644
--- a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt
@@ -16,9 +16,10 @@
package com.android.server.permission.access.appop
-import com.android.server.permission.access.AccessState
import com.android.server.permission.access.AccessUri
import com.android.server.permission.access.AppOpUri
+import com.android.server.permission.access.GetStateScope
+import com.android.server.permission.access.MutateStateScope
import com.android.server.permission.access.PackageUri
import com.android.server.permission.access.UserState
import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
@@ -31,27 +32,23 @@ class PackageAppOpPolicy : BaseAppOpPolicy(PackageAppOpPersistence()) {
override val objectScheme: String
get() = AppOpUri.SCHEME
- override fun getModes(subject: AccessUri, state: AccessState): IndexedMap<String, Int>? {
+ override fun GetStateScope.getModes(subject: AccessUri): IndexedMap<String, Int>? {
subject as PackageUri
return state.userStates[subject.userId]?.packageAppOpModes?.get(subject.packageName)
}
- override fun getOrCreateModes(subject: AccessUri, state: AccessState): IndexedMap<String, Int> {
+ override fun MutateStateScope.getOrCreateModes(subject: AccessUri): IndexedMap<String, Int> {
subject as PackageUri
- return state.userStates.getOrPut(subject.userId) { UserState() }
+ return newState.userStates.getOrPut(subject.userId) { UserState() }
.packageAppOpModes.getOrPut(subject.packageName) { IndexedMap() }
}
- override fun removeModes(subject: AccessUri, state: AccessState) {
+ override fun MutateStateScope.removeModes(subject: AccessUri) {
subject as PackageUri
- state.userStates[subject.userId]?.packageAppOpModes?.remove(subject.packageName)
+ newState.userStates[subject.userId]?.packageAppOpModes?.remove(subject.packageName)
}
- override fun onPackageRemoved(
- packageState: PackageState,
- oldState: AccessState,
- newState: AccessState
- ) {
+ override fun MutateStateScope.onPackageRemoved(packageState: PackageState) {
newState.userStates.forEachIndexed { _, _, userState ->
userState.packageAppOpModes -= packageState.packageName
}
diff --git a/services/permission/java/com/android/server/permission/access/appop/UidAppOpPolicy.kt b/services/permission/java/com/android/server/permission/access/appop/UidAppOpPolicy.kt
index 26d01144609a..862db8f35390 100644
--- a/services/permission/java/com/android/server/permission/access/appop/UidAppOpPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/UidAppOpPolicy.kt
@@ -16,9 +16,10 @@
package com.android.server.permission.access.appop
-import com.android.server.permission.access.AccessState
import com.android.server.permission.access.AccessUri
import com.android.server.permission.access.AppOpUri
+import com.android.server.permission.access.GetStateScope
+import com.android.server.permission.access.MutateStateScope
import com.android.server.permission.access.UidUri
import com.android.server.permission.access.UserState
import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
@@ -30,23 +31,23 @@ class UidAppOpPolicy : BaseAppOpPolicy(UidAppOpPersistence()) {
override val objectScheme: String
get() = AppOpUri.SCHEME
- override fun getModes(subject: AccessUri, state: AccessState): IndexedMap<String, Int>? {
+ override fun GetStateScope.getModes(subject: AccessUri): IndexedMap<String, Int>? {
subject as UidUri
return state.userStates[subject.userId]?.uidAppOpModes?.get(subject.appId)
}
- override fun getOrCreateModes(subject: AccessUri, state: AccessState): IndexedMap<String, Int> {
+ override fun MutateStateScope.getOrCreateModes(subject: AccessUri): IndexedMap<String, Int> {
subject as UidUri
- return state.userStates.getOrPut(subject.userId) { UserState() }
+ return newState.userStates.getOrPut(subject.userId) { UserState() }
.uidAppOpModes.getOrPut(subject.appId) { IndexedMap() }
}
- override fun removeModes(subject: AccessUri, state: AccessState) {
+ override fun MutateStateScope.removeModes(subject: AccessUri) {
subject as UidUri
- state.userStates[subject.userId]?.uidAppOpModes?.remove(subject.appId)
+ newState.userStates[subject.userId]?.uidAppOpModes?.remove(subject.appId)
}
- override fun onAppIdRemoved(appId: Int, oldState: AccessState, newState: AccessState) {
+ override fun MutateStateScope.onAppIdRemoved(appId: Int) {
newState.userStates.forEachIndexed { _, _, userState ->
userState.uidAppOpModes -= appId
}
diff --git a/services/permission/java/com/android/server/permission/access/permission/ModernPermissionManagerServiceImpl.kt b/services/permission/java/com/android/server/permission/access/permission/ModernPermissionManagerServiceImpl.kt
new file mode 100644
index 000000000000..cc51866b4695
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/permission/ModernPermissionManagerServiceImpl.kt
@@ -0,0 +1,456 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.permission.access.permission
+
+import android.content.pm.PackageManager
+import android.content.pm.PackageManagerInternal
+import android.content.pm.PermissionGroupInfo
+import android.content.pm.PermissionInfo
+import android.content.pm.permission.SplitPermissionInfoParcelable
+import android.os.Binder
+import android.os.Build
+import android.os.Process
+import android.os.UserHandle
+import android.permission.IOnPermissionsChangeListener
+import com.android.server.LocalManagerRegistry
+import com.android.server.LocalServices
+import com.android.server.pm.PackageManagerLocal
+import com.android.server.pm.permission.PermissionManagerServiceInterface
+import com.android.server.permission.access.AccessCheckingService
+import com.android.server.permission.access.PermissionUri
+import com.android.server.permission.access.UidUri
+import com.android.server.permission.access.data.Permission
+import com.android.server.permission.access.util.hasBits
+import com.android.server.pm.permission.LegacyPermission
+import com.android.server.pm.permission.LegacyPermissionSettings
+import com.android.server.pm.permission.LegacyPermissionState
+import com.android.server.pm.permission.PermissionManagerServiceInternal
+import com.android.server.pm.pkg.AndroidPackage
+import java.io.FileDescriptor
+import java.io.PrintWriter
+
+/**
+ * Modern implementation of [PermissionManagerServiceInterface].
+ */
+class ModernPermissionManagerServiceImpl(
+ private val service: AccessCheckingService
+) : PermissionManagerServiceInterface {
+ private val policy =
+ service.getSchemePolicy(UidUri.SCHEME, PermissionUri.SCHEME) as UidPermissionPolicy
+
+ private val packageManagerInternal =
+ LocalServices.getService(PackageManagerInternal::class.java)
+
+ private val packageManagerLocal =
+ LocalManagerRegistry.getManagerOrThrow(PackageManagerLocal::class.java)
+
+ override fun getAllPermissionGroups(flags: Int): List<PermissionGroupInfo> {
+ TODO("Not yet implemented")
+ }
+
+ override fun getPermissionGroupInfo(
+ permissionGroupName: String,
+ flags: Int
+ ): PermissionGroupInfo? {
+ val permissionGroup: PermissionGroupInfo
+ packageManagerLocal.withUnfilteredSnapshot().use { snapshot ->
+ val callingUid = Binder.getCallingUid()
+ if (snapshot.isUidInstantApp(callingUid)) {
+ return null
+ }
+
+ permissionGroup = service.getState {
+ with(policy) { getPermissionGroup(permissionGroupName) }
+ } ?: return null
+
+ val isPermissionGroupVisible =
+ snapshot.isPackageVisibleToUid(permissionGroup.packageName, callingUid)
+ if (!isPermissionGroupVisible) {
+ return null
+ }
+ }
+
+ return permissionGroup.generatePermissionGroupInfo(flags)
+ }
+
+ /**
+ * Generate a new [PermissionGroupInfo] from [PermissionGroupInfo] and adjust it accordingly.
+ */
+ private fun PermissionGroupInfo.generatePermissionGroupInfo(flags: Int): PermissionGroupInfo =
+ @Suppress("DEPRECATION")
+ PermissionGroupInfo(this).apply {
+ if (!flags.hasBits(PackageManager.GET_META_DATA)) {
+ metaData = null
+ }
+ }
+
+ override fun getPermissionInfo(
+ permissionName: String,
+ flags: Int,
+ opPackageName: String
+ ): PermissionInfo? {
+ val permission: Permission
+ val targetSdkVersion: Int
+ packageManagerLocal.withUnfilteredSnapshot().use { snapshot ->
+ val callingUid = Binder.getCallingUid()
+ if (snapshot.isUidInstantApp(callingUid)) {
+ return null
+ }
+
+ permission = service.getState {
+ with(policy) { getPermission(permissionName) }
+ } ?: return null
+
+ val isPermissionVisible =
+ snapshot.isPackageVisibleToUid(permission.packageName, callingUid)
+ if (!isPermissionVisible) {
+ return null
+ }
+
+ val callingAppId = UserHandle.getAppId(callingUid)
+ val opPackage = snapshot.packageStates[opPackageName]?.androidPackage
+ targetSdkVersion = when {
+ // System sees all flags.
+ callingAppId == Process.ROOT_UID || callingAppId == Process.SYSTEM_UID ||
+ callingAppId == Process.SHELL_UID -> Build.VERSION_CODES.CUR_DEVELOPMENT
+ opPackage != null -> opPackage.targetSdkVersion
+ else -> Build.VERSION_CODES.CUR_DEVELOPMENT
+ }
+ }
+
+ return permission.generatePermissionInfo(flags, targetSdkVersion)
+ }
+
+ /**
+ * Generate a new [PermissionInfo] from [Permission] and adjust it accordingly.
+ */
+ private fun Permission.generatePermissionInfo(
+ flags: Int,
+ targetSdkVersion: Int
+ ): PermissionInfo =
+ @Suppress("DEPRECATION")
+ PermissionInfo(permissionInfo).apply {
+ if (!flags.hasBits(PackageManager.GET_META_DATA)) {
+ metaData = null
+ }
+ if (targetSdkVersion < Build.VERSION_CODES.O) {
+ val protection = protection
+ // Signature permission's protection flags are always reported.
+ if (protection != PermissionInfo.PROTECTION_SIGNATURE) {
+ protectionLevel = protection
+ }
+ }
+ }
+
+ override fun queryPermissionsByGroup(
+ permissionGroupName: String,
+ flags: Int
+ ): List<PermissionInfo> {
+ TODO("Not yet implemented")
+ }
+
+ override fun getAllPermissionsWithProtection(protection: Int): List<PermissionInfo> {
+ TODO("Not yet implemented")
+ }
+
+ override fun getAllPermissionsWithProtectionFlags(protectionFlags: Int): List<PermissionInfo> {
+ TODO("Not yet implemented")
+ }
+
+ override fun getPermissionGids(permissionName: String, userId: Int): IntArray {
+ TODO("Not yet implemented")
+ }
+
+ override fun addPermission(permissionInfo: PermissionInfo, async: Boolean): Boolean {
+ TODO("Not yet implemented")
+ }
+
+ override fun removePermission(permissionName: String) {
+ TODO("Not yet implemented")
+ }
+
+ override fun checkPermission(packageName: String, permissionName: String, userId: Int): Int {
+ TODO("Not yet implemented")
+ }
+
+ override fun checkUidPermission(uid: Int, permissionName: String): Int {
+ TODO("Not yet implemented")
+ }
+
+ override fun getGrantedPermissions(packageName: String, userId: Int): Set<String> {
+ TODO("Not yet implemented")
+ }
+
+ override fun grantRuntimePermission(packageName: String, permissionName: String, userId: Int) {
+ TODO("Not yet implemented")
+ }
+
+ override fun revokeRuntimePermission(
+ packageName: String,
+ permissionName: String,
+ userId: Int,
+ reason: String?
+ ) {
+ TODO("Not yet implemented")
+ }
+
+ override fun revokePostNotificationPermissionWithoutKillForTest(
+ packageName: String,
+ userId: Int
+ ) {
+ TODO("Not yet implemented")
+ }
+
+ override fun addOnPermissionsChangeListener(listener: IOnPermissionsChangeListener) {
+ TODO("Not yet implemented")
+ }
+
+ override fun removeOnPermissionsChangeListener(listener: IOnPermissionsChangeListener) {
+ TODO("Not yet implemented")
+ }
+
+ override fun getPermissionFlags(packageName: String, permissionName: String, userId: Int): Int {
+ TODO("Not yet implemented")
+ }
+
+ override fun isPermissionRevokedByPolicy(
+ packageName: String,
+ permissionName: String,
+ userId: Int
+ ): Boolean {
+ TODO("Not yet implemented")
+ }
+
+ override fun isPermissionsReviewRequired(packageName: String, userId: Int): Boolean {
+ TODO("Not yet implemented")
+ }
+
+ override fun shouldShowRequestPermissionRationale(
+ packageName: String,
+ permissionName: String,
+ userId: Int
+ ): Boolean {
+ TODO("Not yet implemented")
+ }
+
+ override fun updatePermissionFlags(
+ packageName: String,
+ permissionName: String,
+ flagMask: Int,
+ flagValues: Int,
+ checkAdjustPolicyFlagPermission: Boolean,
+ userId: Int
+ ) {
+ TODO("Not yet implemented")
+ }
+
+ override fun updatePermissionFlagsForAllApps(flagMask: Int, flagValues: Int, userId: Int) {
+ TODO("Not yet implemented")
+ }
+
+ override fun addAllowlistedRestrictedPermission(
+ packageName: String,
+ permissionName: String,
+ flags: Int,
+ userId: Int
+ ): Boolean {
+ TODO("Not yet implemented")
+ }
+
+ override fun getAllowlistedRestrictedPermissions(
+ packageName: String,
+ flags: Int,
+ userId: Int
+ ): MutableList<String> {
+ TODO("Not yet implemented")
+ }
+
+ override fun removeAllowlistedRestrictedPermission(
+ packageName: String,
+ permissionName: String,
+ flags: Int,
+ userId: Int
+ ): Boolean {
+ TODO("Not yet implemented")
+ }
+
+ override fun resetRuntimePermissions(androidPackage: AndroidPackage, userId: Int) {
+ TODO("Not yet implemented")
+ }
+
+ override fun resetRuntimePermissionsForUser(userId: Int) {
+ TODO("Not yet implemented")
+ }
+
+ override fun addOnRuntimePermissionStateChangedListener(
+ listener: PermissionManagerServiceInternal.OnRuntimePermissionStateChangedListener
+ ) {
+ TODO("Not yet implemented")
+ }
+
+ override fun removeOnRuntimePermissionStateChangedListener(
+ listener: PermissionManagerServiceInternal.OnRuntimePermissionStateChangedListener
+ ) {
+ TODO("Not yet implemented")
+ }
+
+ override fun getSplitPermissions(): List<SplitPermissionInfoParcelable> {
+ TODO("Not yet implemented")
+ }
+
+ override fun getAppOpPermissionPackages(permissionName: String): Array<String> {
+ TODO("Not yet implemented")
+ }
+
+ override fun getAllAppOpPermissionPackages(): Map<String, Set<String>> {
+ TODO("Not yet implemented")
+ }
+
+ override fun getGidsForUid(uid: Int): IntArray {
+ TODO("Not yet implemented")
+ }
+
+ override fun backupRuntimePermissions(userId: Int): ByteArray? {
+ TODO("Not yet implemented")
+ }
+
+ override fun restoreRuntimePermissions(backup: ByteArray, userId: Int) {
+ TODO("Not yet implemented")
+ }
+
+ override fun restoreDelayedRuntimePermissions(packageName: String, userId: Int) {
+ TODO("Not yet implemented")
+ }
+
+ override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>?) {
+ TODO("Not yet implemented")
+ }
+
+ override fun getPermissionTEMP(
+ permissionName: String
+ ): com.android.server.pm.permission.Permission? {
+ TODO("Not yet implemented")
+ }
+
+ override fun getLegacyPermissions(): List<LegacyPermission> {
+ TODO("Not yet implemented")
+ }
+
+ override fun readLegacyPermissionsTEMP(legacyPermissionSettings: LegacyPermissionSettings) {
+ TODO("Not yet implemented")
+ }
+
+ override fun writeLegacyPermissionsTEMP(legacyPermissionSettings: LegacyPermissionSettings) {
+ TODO("Not yet implemented")
+ }
+
+ override fun getLegacyPermissionState(appId: Int): LegacyPermissionState {
+ TODO("Not yet implemented")
+ }
+
+ override fun readLegacyPermissionStateTEMP() {
+ TODO("Not yet implemented")
+ }
+
+ override fun writeLegacyPermissionStateTEMP() {
+ TODO("Not yet implemented")
+ }
+
+ override fun onSystemReady() {
+ TODO("Not yet implemented")
+ }
+
+ override fun onUserCreated(userId: Int) {
+ TODO("Not yet implemented")
+ }
+
+ override fun onUserRemoved(userId: Int) {
+ TODO("Not yet implemented")
+ }
+
+ override fun onStorageVolumeMounted(volumeUuid: String, fingerprintChanged: Boolean) {
+ TODO("Not yet implemented")
+ }
+
+ override fun onPackageAdded(
+ androidPackage: AndroidPackage,
+ isInstantApp: Boolean,
+ oldPackage: AndroidPackage?
+ ) {
+ TODO("Not yet implemented")
+ }
+
+ override fun onPackageInstalled(
+ androidPackage: AndroidPackage,
+ previousAppId: Int,
+ params: PermissionManagerServiceInternal.PackageInstalledParams,
+ userId: Int
+ ) {
+ TODO("Not yet implemented")
+ }
+
+ override fun onPackageUninstalled(
+ packageName: String,
+ appId: Int,
+ androidPackage: AndroidPackage?,
+ sharedUserPkgs: MutableList<AndroidPackage>,
+ userId: Int
+ ) {
+ TODO("Not yet implemented")
+ }
+
+ override fun onPackageRemoved(androidPackage: AndroidPackage) {
+ TODO("Not yet implemented")
+ }
+
+ /**
+ * Check whether a UID belongs to an instant app.
+ */
+ private fun PackageManagerLocal.UnfilteredSnapshot.isUidInstantApp(uid: Int): Boolean {
+ if (Process.isIsolatedUid(uid)) {
+ // Unfortunately we don't have the API for getting the owner UID of an isolated UID yet,
+ // so for now we just keep calling the old API.
+ return packageManagerInternal.getInstantAppPackageName(uid) != null
+ }
+ val appId = UserHandle.getAppId(uid)
+ // Instant apps can't have shared UIDs, so we can just take the first package.
+ val firstPackageState = packageStates.values.firstOrNull { it.appId == appId }
+ ?: return false
+ val userId = UserHandle.getUserId(uid)
+ return firstPackageState.getUserStateOrDefault(userId).isInstantApp
+ }
+
+ /**
+ * Check whether a package is visible to a UID within the same user as the UID.
+ */
+ private fun PackageManagerLocal.UnfilteredSnapshot.isPackageVisibleToUid(
+ packageName: String,
+ uid: Int
+ ): Boolean = isPackageVisibleToUid(packageName, UserHandle.getUserId(uid), uid)
+
+ /**
+ * Check whether a package in a particular user is visible to a UID.
+ */
+ private fun PackageManagerLocal.UnfilteredSnapshot.isPackageVisibleToUid(
+ packageName: String,
+ userId: Int,
+ uid: Int
+ ): Boolean {
+ val user = UserHandle.of(userId)
+ return filtered(uid, user).use { it.getPackageState(packageName) != null }
+ }
+}
diff --git a/services/permission/java/com/android/server/permission/access/permission/UidPermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/UidPermissionPolicy.kt
index 6479e6ad88e3..e08192444d67 100644
--- a/services/permission/java/com/android/server/permission/access/permission/UidPermissionPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/UidPermissionPolicy.kt
@@ -18,6 +18,7 @@ package com.android.server.permission.access.permission
import android.Manifest
import android.content.pm.PackageManager
+import android.content.pm.PermissionGroupInfo
import android.content.pm.PermissionInfo
import android.os.Build
import android.os.UserHandle
@@ -26,6 +27,8 @@ import com.android.modules.utils.BinaryXmlPullParser
import com.android.modules.utils.BinaryXmlSerializer
import com.android.server.permission.access.AccessState
import com.android.server.permission.access.AccessUri
+import com.android.server.permission.access.GetStateScope
+import com.android.server.permission.access.MutateStateScope
import com.android.server.permission.access.PermissionUri
import com.android.server.permission.access.SchemePolicy
import com.android.server.permission.access.SystemState
@@ -52,19 +55,17 @@ class UidPermissionPolicy : SchemePolicy() {
override val objectScheme: String
get() = PermissionUri.SCHEME
- override fun getDecision(subject: AccessUri, `object`: AccessUri, state: AccessState): Int {
+ override fun GetStateScope.getDecision(subject: AccessUri, `object`: AccessUri): Int {
subject as UidUri
`object` as PermissionUri
return state.userStates[subject.userId]?.permissionFlags?.get(subject.appId)
?.get(`object`.permissionName) ?: 0
}
- override fun setDecision(
+ override fun MutateStateScope.setDecision(
subject: AccessUri,
`object`: AccessUri,
- decision: Int,
- oldState: AccessState,
- newState: AccessState
+ decision: Int
) {
subject as UidUri
`object` as PermissionUri
@@ -73,64 +74,58 @@ class UidPermissionPolicy : SchemePolicy() {
uidFlags[`object`.permissionName] = decision
}
- override fun onUserAdded(userId: Int, oldState: AccessState, newState: AccessState) {
+ override fun MutateStateScope.onUserAdded(userId: Int) {
newState.systemState.packageStates.forEachValueIndexed { _, packageState ->
- evaluateAllPermissionStatesForPackageAndUser(
- packageState, null, userId, oldState, newState
- )
- grantImplicitPermissions(packageState, userId, oldState, newState)
+ evaluateAllPermissionStatesForPackageAndUser(packageState, null, userId)
+ grantImplicitPermissions(packageState, userId)
}
}
- override fun onAppIdAdded(appId: Int, oldState: AccessState, newState: AccessState) {
+ override fun MutateStateScope.onAppIdAdded(appId: Int) {
newState.userStates.forEachIndexed { _, _, userState ->
userState.permissionFlags.getOrPut(appId) { IndexedMap() }
}
}
- override fun onAppIdRemoved(appId: Int, oldState: AccessState, newState: AccessState) {
+ override fun MutateStateScope.onAppIdRemoved(appId: Int) {
newState.userStates.forEachIndexed { _, _, userState -> userState.permissionFlags -= appId }
}
- override fun onPackageAdded(
- packageState: PackageState,
- oldState: AccessState,
- newState: AccessState
- ) {
+ override fun MutateStateScope.onPackageAdded(packageState: PackageState) {
val changedPermissionNames = IndexedSet<String>()
- adoptPermissions(packageState, changedPermissionNames, newState)
- addPermissionGroups(packageState, newState)
- addPermissions(packageState, changedPermissionNames, newState)
+ adoptPermissions(packageState, changedPermissionNames)
+ addPermissionGroups(packageState)
+ addPermissions(packageState, changedPermissionNames)
// TODO: revokeStoragePermissionsIfScopeExpandedInternal()
- trimPermissions(packageState.packageName, newState)
- changedPermissionNames.forEachIndexed { _, it ->
- evaluatePermissionStateForAllPackages(it, packageState, oldState, newState)
+ trimPermissions(packageState.packageName)
+ changedPermissionNames.forEachIndexed { _, permissionName ->
+ evaluatePermissionStateForAllPackages(permissionName, packageState)
}
- evaluateAllPermissionStatesForPackage(packageState, packageState, oldState, newState)
- newState.systemState.userIds.forEachIndexed { _, it ->
- grantImplicitPermissions(packageState, it, oldState, newState)
+ evaluateAllPermissionStatesForPackage(packageState, packageState)
+ newState.systemState.userIds.forEachIndexed { _, userId ->
+ grantImplicitPermissions(packageState, userId)
}
// TODO: add trimPermissionStates() here for removing the permission states that are
// no longer requested. (equivalent to revokeUnusedSharedUserPermissionsLocked())
}
- private fun adoptPermissions(
+ private fun MutateStateScope.adoptPermissions(
packageState: PackageState,
- changedPermissionNames: IndexedSet<String>,
- newState: AccessState
+ changedPermissionNames: IndexedSet<String>
) {
val `package` = packageState.androidPackage!!
`package`.adoptPermissions.forEachIndexed { _, originalPackageName ->
val packageName = `package`.packageName
- if (!canAdoptPermissions(packageName, originalPackageName, newState)) {
+ if (!canAdoptPermissions(packageName, originalPackageName)) {
return@forEachIndexed
}
newState.systemState.permissions.let { permissions ->
- permissions.forEachIndexed { i, permissionName, oldPermission ->
+ permissions.forEachIndexed permissions@ {
+ permissionIndex, permissionName, oldPermission ->
if (oldPermission.packageName != originalPackageName) {
- return@forEachIndexed
+ return@permissions
}
@Suppress("DEPRECATION")
val newPermissionInfo = PermissionInfo().apply {
@@ -140,16 +135,15 @@ class UidPermissionPolicy : SchemePolicy() {
}
val newPermission = Permission(newPermissionInfo, false, oldPermission.type, 0)
changedPermissionNames += permissionName
- permissions.setValueAt(i, newPermission)
+ permissions.setValueAt(permissionIndex, newPermission)
}
}
}
}
- private fun canAdoptPermissions(
+ private fun MutateStateScope.canAdoptPermissions(
packageName: String,
- originalPackageName: String,
- newState: AccessState
+ originalPackageName: String
): Boolean {
val originalPackageState = newState.systemState.packageStates[originalPackageName]
?: return false
@@ -170,7 +164,7 @@ class UidPermissionPolicy : SchemePolicy() {
return true
}
- private fun addPermissionGroups(packageState: PackageState, newState: AccessState) {
+ private fun MutateStateScope.addPermissionGroups(packageState: PackageState) {
// Different from the old implementation, which decides whether the app is an instant app by
// the install flags, now for consistent behavior we allow adding permission groups if the
// app is non-instant in at least one user.
@@ -202,10 +196,9 @@ class UidPermissionPolicy : SchemePolicy() {
}
}
- private fun addPermissions(
+ private fun MutateStateScope.addPermissions(
packageState: PackageState,
- changedPermissionNames: IndexedSet<String>,
- newState: AccessState
+ changedPermissionNames: IndexedSet<String>
) {
packageState.androidPackage!!.permissions.forEachIndexed { _, parsedPermission ->
// TODO:
@@ -229,7 +222,7 @@ class UidPermissionPolicy : SchemePolicy() {
// Different from the old implementation, which may add an (incomplete) signature
// permission inside another package's permission tree, we now consistently ignore such
// permissions.
- val permissionTree = getPermissionTree(permissionName, newState)
+ val permissionTree = getPermissionTree(permissionName)
val newPackageName = newPermissionInfo.packageName
if (permissionTree != null && newPackageName != permissionTree.packageName) {
Log.w(
@@ -294,10 +287,7 @@ class UidPermissionPolicy : SchemePolicy() {
}
}
- private fun trimPermissions(
- packageName: String,
- newState: AccessState,
- ) {
+ private fun MutateStateScope.trimPermissions(packageName: String) {
val packageState = newState.systemState.packageStates[packageName]
val androidPackage = packageState?.androidPackage
if (packageState != null && androidPackage == null) {
@@ -314,20 +304,20 @@ class UidPermissionPolicy : SchemePolicy() {
}
newState.systemState.permissions.removeAllIndexed { i, permissionName, permission ->
- val updatedPermission = updatePermissionIfDynamic(permission, newState)
+ val updatedPermission = updatePermissionIfDynamic(permission)
newState.systemState.permissions.setValueAt(i, updatedPermission)
if (updatedPermission.packageName == packageName && (
packageState == null || androidPackage!!.permissions.noneIndexed { _, it ->
!it.isTree && it.name == permissionName
}
)) {
- if (!isPermissionDeclaredByDisabledSystemPackage(permission, newState)) {
+ if (!isPermissionDeclaredByDisabledSystemPackage(permission)) {
newState.userStates.forEachIndexed { _, userId, userState ->
userState.permissionFlags.forEachKeyIndexed { _, appId ->
setPermissionFlags(
appId, permissionName, getPermissionFlags(
- appId, permissionName, userId, newState
- ) and PermissionFlags.INSTALL_REVOKED, userId, newState
+ appId, permissionName, userId
+ ) and PermissionFlags.INSTALL_REVOKED, userId
)
}
}
@@ -339,9 +329,8 @@ class UidPermissionPolicy : SchemePolicy() {
}
}
- private fun isPermissionDeclaredByDisabledSystemPackage(
- permission: Permission,
- newState: AccessState
+ private fun MutateStateScope.isPermissionDeclaredByDisabledSystemPackage(
+ permission: Permission
): Boolean {
val disabledSystemPackage = newState.systemState
.disabledSystemPackageStates[permission.packageName]?.androidPackage ?: return false
@@ -350,14 +339,11 @@ class UidPermissionPolicy : SchemePolicy() {
}
}
- private fun updatePermissionIfDynamic(
- permission: Permission,
- newState: AccessState
- ): Permission {
+ private fun MutateStateScope.updatePermissionIfDynamic(permission: Permission): Permission {
if (!permission.isDynamic) {
return permission
}
- val permissionTree = getPermissionTree(permission.name, newState) ?: return permission
+ val permissionTree = getPermissionTree(permission.name) ?: return permission
@Suppress("DEPRECATION")
return permission.copy(
permissionInfo = PermissionInfo(permission.permissionInfo).apply {
@@ -366,7 +352,7 @@ class UidPermissionPolicy : SchemePolicy() {
)
}
- private fun getPermissionTree(permissionName: String, newState: AccessState): Permission? =
+ private fun MutateStateScope.getPermissionTree(permissionName: String): Permission? =
newState.systemState.permissionTrees.firstNotNullOfOrNullIndexed {
_, permissionTreeName, permissionTree ->
if (permissionName.startsWith(permissionTreeName) &&
@@ -378,58 +364,48 @@ class UidPermissionPolicy : SchemePolicy() {
}
}
- private fun evaluatePermissionStateForAllPackages(
+ private fun MutateStateScope.evaluatePermissionStateForAllPackages(
permissionName: String,
- installedPackageState: PackageState?,
- oldState: AccessState,
- newState: AccessState
+ installedPackageState: PackageState?
) {
newState.systemState.userIds.forEachIndexed { _, userId ->
oldState.userStates[userId]?.permissionFlags?.forEachIndexed {
_, appId, permissionFlags ->
if (permissionName in permissionFlags) {
- evaluatePermissionState(
- appId, permissionName, installedPackageState, userId, oldState, newState
- )
+ evaluatePermissionState(appId, permissionName, installedPackageState, userId)
}
}
}
}
- private fun evaluateAllPermissionStatesForPackage(
+ private fun MutateStateScope.evaluateAllPermissionStatesForPackage(
packageState: PackageState,
- installedPackageState: PackageState?,
- oldState: AccessState,
- newState: AccessState
+ installedPackageState: PackageState?
) {
newState.systemState.userIds.forEachIndexed { _, userId ->
evaluateAllPermissionStatesForPackageAndUser(
- packageState, installedPackageState, userId, oldState, newState
+ packageState, installedPackageState, userId
)
}
}
- private fun evaluateAllPermissionStatesForPackageAndUser(
+ private fun MutateStateScope.evaluateAllPermissionStatesForPackageAndUser(
packageState: PackageState,
installedPackageState: PackageState?,
- userId: Int,
- oldState: AccessState,
- newState: AccessState
+ userId: Int
) {
- packageState.androidPackage?.requestedPermissions?.forEachIndexed { _, it ->
+ packageState.androidPackage?.requestedPermissions?.forEachIndexed { _, permissionName ->
evaluatePermissionState(
- packageState.appId, it, installedPackageState, userId, oldState, newState
+ packageState.appId, permissionName, installedPackageState, userId
)
}
}
- private fun evaluatePermissionState(
+ private fun MutateStateScope.evaluatePermissionState(
appId: Int,
permissionName: String,
installedPackageState: PackageState?,
- userId: Int,
- oldState: AccessState,
- newState: AccessState
+ userId: Int
) {
val packageNames = newState.systemState.appIds[appId]
val hasMissingPackage = packageNames.anyIndexed { _, packageName ->
@@ -440,17 +416,17 @@ class UidPermissionPolicy : SchemePolicy() {
return
}
val permission = newState.systemState.permissions[permissionName] ?: return
- val oldFlags = getPermissionFlags(appId, permissionName, userId, newState)
+ val oldFlags = getPermissionFlags(appId, permissionName, userId)
if (permission.isNormal) {
val wasGranted = oldFlags.hasBits(PermissionFlags.INSTALL_GRANTED)
if (!wasGranted) {
val wasRevoked = oldFlags.hasBits(PermissionFlags.INSTALL_REVOKED)
val isRequestedByInstalledPackage = installedPackageState != null &&
permissionName in installedPackageState.androidPackage!!.requestedPermissions
- val isRequestedBySystemPackage = anyPackageInAppId(appId, newState) {
+ val isRequestedBySystemPackage = anyPackageInAppId(appId) {
it.isSystem && permissionName in it.androidPackage!!.requestedPermissions
}
- val isCompatibilityPermission = anyPackageInAppId(appId, newState) {
+ val isCompatibilityPermission = anyPackageInAppId(appId) {
isCompatibilityPermissionForPackage(it.androidPackage!!, permissionName)
}
// If this is an existing, non-system package,
@@ -462,7 +438,7 @@ class UidPermissionPolicy : SchemePolicy() {
} else {
PermissionFlags.INSTALL_REVOKED
}
- setPermissionFlags(appId, permissionName, newFlags, userId, newState)
+ setPermissionFlags(appId, permissionName, newFlags, userId)
}
} else if (permission.isSignature || permission.isInternal) {
val wasProtectionGranted = oldFlags.hasBits(PermissionFlags.PROTECTION_GRANTED)
@@ -471,17 +447,17 @@ class UidPermissionPolicy : SchemePolicy() {
PermissionFlags.PROTECTION_GRANTED
} else {
val mayGrantByPrivileged = !permission.isPrivileged || (
- anyPackageInAppId(appId, newState) {
- checkPrivilegedPermissionAllowlist(it, permission, newState)
+ anyPackageInAppId(appId) {
+ checkPrivilegedPermissionAllowlist(it, permission)
}
)
val shouldGrantBySignature = permission.isSignature && (
- anyPackageInAppId(appId, newState) {
- shouldGrantPermissionBySignature(it, permission, newState)
+ anyPackageInAppId(appId) {
+ shouldGrantPermissionBySignature(it, permission)
}
)
- val shouldGrantByProtectionFlags = anyPackageInAppId(appId, newState) {
- shouldGrantPermissionByProtectionFlags(it, permission, newState)
+ val shouldGrantByProtectionFlags = anyPackageInAppId(appId) {
+ shouldGrantPermissionByProtectionFlags(it, permission)
}
if (mayGrantByPrivileged &&
(shouldGrantBySignature || shouldGrantByProtectionFlags)) {
@@ -501,7 +477,7 @@ class UidPermissionPolicy : SchemePolicy() {
if (permission.isRole) {
newFlags = newFlags or (oldFlags and PermissionFlags.ROLE_GRANTED)
}
- setPermissionFlags(appId, permissionName, newFlags, userId, newState)
+ setPermissionFlags(appId, permissionName, newFlags, userId)
} else if (permission.isRuntime) {
// TODO: add runtime permissions
} else {
@@ -513,12 +489,7 @@ class UidPermissionPolicy : SchemePolicy() {
// TODO: revokePermissionsNoLongerImplicitLocked() for runtime permissions
}
- private fun grantImplicitPermissions(
- packageState: PackageState,
- userId: Int,
- oldState: AccessState,
- newState: AccessState
- ) {
+ private fun MutateStateScope.grantImplicitPermissions(packageState: PackageState, userId: Int) {
val appId = packageState.appId
val androidPackage = packageState.androidPackage ?: return
androidPackage.implicitPermissions.forEachIndexed implicitPermissions@ {
@@ -530,6 +501,7 @@ class UidPermissionPolicy : SchemePolicy() {
if (!implicitPermission.isRuntime) {
return@implicitPermissions
}
+ // Explicitly check against the old state to determine if this permission is new.
val isNewPermission = getPermissionFlags(
appId, implicitPermissionName, userId, oldState
) == 0
@@ -544,7 +516,7 @@ class UidPermissionPolicy : SchemePolicy() {
checkNotNull(sourcePermission) {
"Unknown source permission $sourcePermissionName in split permissions"
}
- val sourceFlags = getPermissionFlags(appId, sourcePermissionName, userId, newState)
+ val sourceFlags = getPermissionFlags(appId, sourcePermissionName, userId)
val isSourceGranted = sourceFlags.hasAnyBit(PermissionFlags.MASK_GRANTED)
val isNewGranted = newFlags.hasAnyBit(PermissionFlags.MASK_GRANTED)
val isGrantingNewFromRevoke = isSourceGranted && !isNewGranted
@@ -559,23 +531,22 @@ class UidPermissionPolicy : SchemePolicy() {
}
}
newFlags = newFlags or PermissionFlags.IMPLICIT
- setPermissionFlags(appId, implicitPermissionName, newFlags, userId, newState)
+ setPermissionFlags(appId, implicitPermissionName, newFlags, userId)
}
}
- private fun getPermissionFlags(
+ private fun MutateStateScope.getPermissionFlags(
appId: Int,
permissionName: String,
userId: Int,
- state: AccessState
+ state: AccessState = newState
): Int = state.userStates[userId].permissionFlags[appId].getWithDefault(permissionName, 0)
- private fun setPermissionFlags(
+ private fun MutateStateScope.setPermissionFlags(
appId: Int,
permissionName: String,
flags: Int,
- userId: Int,
- newState: AccessState
+ userId: Int
) {
newState.userStates[userId].permissionFlags[appId]!!
.putWithDefault(permissionName, flags, 0)
@@ -585,8 +556,9 @@ class UidPermissionPolicy : SchemePolicy() {
androidPackage: AndroidPackage,
permissionName: String
): Boolean {
- for (info: CompatibilityPermissionInfo in CompatibilityPermissionInfo.COMPAT_PERMS) {
- if (info.name == permissionName && androidPackage.targetSdkVersion < info.sdkVersion) {
+ for (compatibilityPermission in CompatibilityPermissionInfo.COMPAT_PERMS) {
+ if (compatibilityPermission.name == permissionName &&
+ androidPackage.targetSdkVersion < compatibilityPermission.sdkVersion) {
Log.i(
LOG_TAG, "Auto-granting $permissionName to old package" +
" ${androidPackage.packageName}"
@@ -597,10 +569,9 @@ class UidPermissionPolicy : SchemePolicy() {
return false
}
- private fun shouldGrantPermissionBySignature(
+ private fun MutateStateScope.shouldGrantPermissionBySignature(
packageState: PackageState,
- permission: Permission,
- newState: AccessState
+ permission: Permission
): Boolean {
// check if the package is allow to use this signature permission. A package is allowed to
// use a signature permission if:
@@ -622,10 +593,9 @@ class UidPermissionPolicy : SchemePolicy() {
SigningDetails.CertCapabilities.PERMISSION)
}
- private fun checkPrivilegedPermissionAllowlist(
+ private fun MutateStateScope.checkPrivilegedPermissionAllowlist(
packageState: PackageState,
- permission: Permission,
- newState: AccessState
+ permission: Permission
): Boolean {
if (RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_DISABLE) {
return true
@@ -641,10 +611,10 @@ class UidPermissionPolicy : SchemePolicy() {
newState.systemState.privilegedPermissionAllowlistSourcePackageNames) {
return true
}
- if (isInSystemConfigPrivAppPermissions(androidPackage, permission.name, newState)) {
+ if (isInSystemConfigPrivAppPermissions(androidPackage, permission.name)) {
return true
}
- if (isInSystemConfigPrivAppDenyPermissions(androidPackage, permission.name, newState)) {
+ if (isInSystemConfigPrivAppDenyPermissions(androidPackage, permission.name)) {
return false
}
// Updated system apps do not need to be allowlisted
@@ -655,10 +625,9 @@ class UidPermissionPolicy : SchemePolicy() {
return !RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE
}
- private fun isInSystemConfigPrivAppPermissions(
+ private fun MutateStateScope.isInSystemConfigPrivAppPermissions(
androidPackage: AndroidPackage,
- permissionName: String,
- newState: AccessState
+ permissionName: String
): Boolean {
val apexModuleName = androidPackage.apexModuleName
val systemState = newState.systemState
@@ -682,10 +651,9 @@ class UidPermissionPolicy : SchemePolicy() {
return permissionNames?.contains(permissionName) == true
}
- private fun isInSystemConfigPrivAppDenyPermissions(
+ private fun MutateStateScope.isInSystemConfigPrivAppDenyPermissions(
androidPackage: AndroidPackage,
- permissionName: String,
- newState: AccessState
+ permissionName: String
): Boolean {
// Different from the previous implementation, which may incorrectly use the APEX package
// name, we now use the APEX module name to be consistent with the allowlist.
@@ -713,22 +681,21 @@ class UidPermissionPolicy : SchemePolicy() {
return permissionNames?.contains(permissionName) == true
}
- private fun anyPackageInAppId(
+ private fun MutateStateScope.anyPackageInAppId(
appId: Int,
- newState: AccessState,
+ state: AccessState = newState,
predicate: (PackageState) -> Boolean
): Boolean {
- val packageNames = newState.systemState.appIds[appId]
+ val packageNames = state.systemState.appIds[appId]
return packageNames.anyIndexed { _, packageName ->
- val packageState = newState.systemState.packageStates[packageName]!!
+ val packageState = state.systemState.packageStates[packageName]!!
packageState.androidPackage != null && predicate(packageState)
}
}
- private fun shouldGrantPermissionByProtectionFlags(
+ private fun MutateStateScope.shouldGrantPermissionByProtectionFlags(
packageState: PackageState,
- permission: Permission,
- newState: AccessState
+ permission: Permission
): Boolean {
val androidPackage = packageState.androidPackage!!
val knownPackages = newState.systemState.knownPackages
@@ -741,11 +708,9 @@ class UidPermissionPolicy : SchemePolicy() {
.disabledSystemPackageStates[packageState.packageName]?.androidPackage
disabledSystemPackage != null &&
permission.name in disabledSystemPackage.requestedPermissions &&
- shouldGrantPrivilegedOrOemPermission(
- disabledSystemPackage, permission, newState
- )
+ shouldGrantPrivilegedOrOemPermission(disabledSystemPackage, permission)
} else {
- shouldGrantPrivilegedOrOemPermission(androidPackage, permission, newState)
+ shouldGrantPrivilegedOrOemPermission(androidPackage, permission)
}
if (shouldGrant) {
return true
@@ -815,7 +780,7 @@ class UidPermissionPolicy : SchemePolicy() {
}
if (permission.isRetailDemo &&
packageName in knownPackages[KnownPackages.PACKAGE_RETAIL_DEMO] &&
- isDeviceOrProfileOwnerUid(packageState.appId, newState)) {
+ isDeviceOrProfileOwnerUid(packageState.appId)) {
// Special permission granted only to the OEM specified retail demo app.
// Note that the original code was passing app ID as UID, so this behavior is kept
// unchanged.
@@ -829,10 +794,9 @@ class UidPermissionPolicy : SchemePolicy() {
return false
}
- private fun shouldGrantPrivilegedOrOemPermission(
+ private fun MutateStateScope.shouldGrantPrivilegedOrOemPermission(
androidPackage: AndroidPackage,
- permission: Permission,
- state: AccessState
+ permission: Permission
): Boolean {
val permissionName = permission.name
val packageName = androidPackage.packageName
@@ -855,7 +819,7 @@ class UidPermissionPolicy : SchemePolicy() {
}
permission.isOem -> {
if (androidPackage.isOem) {
- val isOemAllowlisted = state.systemState
+ val isOemAllowlisted = newState.systemState
.oemPermissions[packageName]?.get(permissionName)
checkNotNull(isOemAllowlisted) {
"OEM permission $permissionName requested by package" +
@@ -868,19 +832,15 @@ class UidPermissionPolicy : SchemePolicy() {
return false
}
- private fun isDeviceOrProfileOwnerUid(uid: Int, state: AccessState): Boolean {
+ private fun MutateStateScope.isDeviceOrProfileOwnerUid(uid: Int): Boolean {
val userId = UserHandle.getUserId(uid)
- val ownerPackageName = state.systemState.deviceAndProfileOwners[userId] ?: return false
- val ownerPackageState = state.systemState.packageStates[ownerPackageName] ?: return false
+ val ownerPackageName = newState.systemState.deviceAndProfileOwners[userId] ?: return false
+ val ownerPackageState = newState.systemState.packageStates[ownerPackageName] ?: return false
val ownerUid = UserHandle.getUid(userId, ownerPackageState.appId)
return uid == ownerUid
}
- override fun onPackageRemoved(
- packageState: PackageState,
- oldState: AccessState,
- newState: AccessState
- ) {
+ override fun MutateStateScope.onPackageRemoved(packageState: PackageState) {
// TODO
}
@@ -892,6 +852,12 @@ class UidPermissionPolicy : SchemePolicy() {
with(persistence) { this@serializeSystemState.serializeSystemState(systemState) }
}
+ fun GetStateScope.getPermissionGroup(permissionGroupName: String): PermissionGroupInfo? =
+ state.systemState.permissionGroups[permissionGroupName]
+
+ fun GetStateScope.getPermission(permissionName: String): Permission? =
+ state.systemState.permissions[permissionName]
+
companion object {
private val LOG_TAG = UidPermissionPolicy::class.java.simpleName
diff --git a/services/tests/InputMethodSystemServerTests/Android.bp b/services/tests/InputMethodSystemServerTests/Android.bp
index 939fb6a9d170..70a5c3f961c4 100644
--- a/services/tests/InputMethodSystemServerTests/Android.bp
+++ b/services/tests/InputMethodSystemServerTests/Android.bp
@@ -28,7 +28,7 @@ android_test {
],
srcs: [
- "src/**/*.java",
+ "src/server/**/*.java",
],
static_libs: [
@@ -60,3 +60,52 @@ android_test {
enabled: false,
},
}
+
+android_test {
+ name: "FrameworksImeTests",
+ defaults: [
+ "modules-utils-testable-device-config-defaults",
+ ],
+
+ srcs: [
+ "src/com/android/inputmethodservice/**/*.java",
+ ],
+
+ manifest: "src/com/android/inputmethodservice/AndroidManifest.xml",
+ test_config: "src/com/android/inputmethodservice/AndroidTest.xml",
+
+ static_libs: [
+ "androidx.test.core",
+ "androidx.test.runner",
+ "androidx.test.espresso.core",
+ "androidx.test.espresso.contrib",
+ "androidx.test.ext.truth",
+ "frameworks-base-testutils",
+ "mockito-target-extended-minus-junit4",
+ "platform-test-annotations",
+ "services.core",
+ "servicestests-core-utils",
+ "servicestests-utils-mockito-extended",
+ "truth-prebuilt",
+ "SimpleImeTestingLib",
+ "SimpleImeImsLib",
+ ],
+
+ libs: [
+ "android.test.mock",
+ "android.test.base",
+ "android.test.runner",
+ ],
+
+ data: [
+ ":SimpleTestIme",
+ ],
+
+ certificate: "platform",
+ platform_apis: true,
+ test_suites: ["device-tests"],
+
+ optimize: {
+ enabled: false,
+ },
+}
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidManifest.xml b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidManifest.xml
new file mode 100644
index 000000000000..0104f7142bea
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidManifest.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.inputmethod.imetests">
+
+ <uses-sdk android:targetSdkVersion="31" />
+
+ <!-- Permissions required for granting and logging -->
+ <uses-permission android:name="android.permission.LOG_COMPAT_CHANGE"/>
+ <uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG"/>
+ <uses-permission android:name="android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG"/>
+ <uses-permission android:name="android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD"/>
+
+ <!-- Permissions for reading system info -->
+ <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
+
+ <application android:debuggable="true">
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <!-- The "targetPackage" reference the instruments APK package, which is the FakeImeApk, while
+ the test package is "com.android.inputmethod.imetests" (FrameworksImeTests.apk).-->
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.apps.inputmethod.simpleime"
+ android:label="Frameworks IME Tests" />
+</manifest>
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidTest.xml b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidTest.xml
new file mode 100644
index 000000000000..6c24d6d6e5a6
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidTest.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<configuration description="Runs Frameworks IME Tests.">
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-suite-tag" value="apct-instrumentation" />
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="install-arg" value="-t" />
+ <option name="test-file-name" value="FrameworksImeTests.apk" />
+ <option name="test-file-name" value="SimpleTestIme.apk" />
+ </target_preparer>
+
+ <option name="test-tag" value="FrameworksImeTests" />
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="com.android.inputmethod.imetests" />
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ <option name="hidden-api-checks" value="false"/>
+ </test>
+
+ <!-- Collect the files in the dump directory for debugging -->
+ <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+ <option name="directory-keys" value="/sdcard/FrameworksImeTests/" />
+ <option name="collect-on-run-ended-only" value="true" />
+ </metrics_collector>
+</configuration>
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
new file mode 100644
index 000000000000..16a98454d135
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
@@ -0,0 +1,276 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethodservice;
+
+import static com.android.compatibility.common.util.SystemUtil.eventually;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.Instrumentation;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.os.RemoteException;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.Until;
+import android.util.Log;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.EditText;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.MediumTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.apps.inputmethod.simpleime.ims.InputMethodServiceWrapper;
+import com.android.apps.inputmethod.simpleime.testing.TestActivity;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+@MediumTest
+public class InputMethodServiceTest {
+ private static final String TAG = "SimpleIMSTest";
+ private static final String INPUT_METHOD_SERVICE_NAME = ".SimpleInputMethodService";
+ private static final String EDIT_TEXT_DESC = "Input box";
+ private static final long TIMEOUT_IN_SECONDS = 3;
+
+ public Instrumentation mInstrumentation;
+ public UiDevice mUiDevice;
+ public Context mContext;
+ public String mTargetPackageName;
+ public TestActivity mActivity;
+ public EditText mEditText;
+ public InputMethodServiceWrapper mInputMethodService;
+ public String mInputMethodId;
+
+ @Before
+ public void setUp() throws Exception {
+ mInstrumentation = InstrumentationRegistry.getInstrumentation();
+ mUiDevice = UiDevice.getInstance(mInstrumentation);
+ mContext = mInstrumentation.getContext();
+ mTargetPackageName = mInstrumentation.getTargetContext().getPackageName();
+ mInputMethodId = getInputMethodId();
+ prepareIme();
+ prepareEditor();
+
+ // Waits for input binding ready.
+ eventually(
+ () -> {
+ mInputMethodService =
+ InputMethodServiceWrapper.getInputMethodServiceWrapperForTesting();
+ assertThat(mInputMethodService).isNotNull();
+
+ // The editor won't bring up keyboard by default.
+ assertThat(mInputMethodService.getCurrentInputStarted()).isTrue();
+ assertThat(mInputMethodService.getCurrentInputViewStarted()).isFalse();
+ });
+ }
+
+ @Test
+ public void testShowHideKeyboard_byUserAction() throws InterruptedException {
+ // Performs click on editor box to bring up the soft keyboard.
+ Log.i(TAG, "Click on EditText.");
+ verifyInputViewStatus(() -> clickOnEditorText(), true /* inputViewStarted */);
+
+ // Press back key to hide soft keyboard.
+ Log.i(TAG, "Press back");
+ verifyInputViewStatus(
+ () -> assertThat(mUiDevice.pressHome()).isTrue(), false /* inputViewStarted */);
+ }
+
+ @Test
+ public void testShowHideKeyboard_byApi() throws InterruptedException {
+ // Triggers to show IME via public API.
+ verifyInputViewStatus(
+ () -> assertThat(mActivity.showImeWithWindowInsetsController()).isTrue(),
+ true /* inputViewStarted */);
+
+ // Triggers to hide IME via public API.
+ // TODO(b/242838873): investigate why WIC#hide(ime()) does not work, likely related to
+ // triggered from IME process.
+ verifyInputViewStatusOnMainSync(
+ () -> assertThat(mActivity.hideImeWithInputMethodManager(0 /* flags */)).isTrue(),
+ false /* inputViewStarted */);
+ }
+
+ @Test
+ public void testShowHideSelf() throws InterruptedException {
+ // IME requests to show itself without any flags: expect shown.
+ Log.i(TAG, "Call IMS#requestShowSelf(0)");
+ verifyInputViewStatusOnMainSync(
+ () -> mInputMethodService.requestShowSelf(0), true /* inputViewStarted */);
+
+ // IME requests to hide itself with flag: HIDE_IMPLICIT_ONLY, expect not hide (shown).
+ Log.i(TAG, "Call IMS#requestHideSelf(InputMethodManager.HIDE_IMPLICIT_ONLY)");
+ verifyInputViewStatusOnMainSync(
+ () -> mInputMethodService.requestHideSelf(InputMethodManager.HIDE_IMPLICIT_ONLY),
+ true /* inputViewStarted */);
+
+ // IME request to hide itself without any flags: expect hidden.
+ Log.i(TAG, "Call IMS#requestHideSelf(0)");
+ verifyInputViewStatusOnMainSync(
+ () -> mInputMethodService.requestHideSelf(0), false /* inputViewStarted */);
+
+ // IME request to show itself with flag SHOW_IMPLICIT: expect shown.
+ Log.i(TAG, "Call IMS#requestShowSelf(InputMethodManager.SHOW_IMPLICIT)");
+ verifyInputViewStatusOnMainSync(
+ () -> mInputMethodService.requestShowSelf(InputMethodManager.SHOW_IMPLICIT),
+ true /* inputViewStarted */);
+
+ // IME request to hide itself with flag: HIDE_IMPLICIT_ONLY, expect hidden.
+ Log.i(TAG, "Call IMS#requestHideSelf(InputMethodManager.HIDE_IMPLICIT_ONLY)");
+ verifyInputViewStatusOnMainSync(
+ () -> mInputMethodService.requestHideSelf(InputMethodManager.HIDE_IMPLICIT_ONLY),
+ false /* inputViewStarted */);
+ }
+
+ private void verifyInputViewStatus(Runnable runnable, boolean inputViewStarted)
+ throws InterruptedException {
+ verifyInputViewStatusInternal(runnable, inputViewStarted, false /*runOnMainSync*/);
+ }
+
+ private void verifyInputViewStatusOnMainSync(Runnable runnable, boolean inputViewStarted)
+ throws InterruptedException {
+ verifyInputViewStatusInternal(runnable, inputViewStarted, true /*runOnMainSync*/);
+ }
+
+ private void verifyInputViewStatusInternal(
+ Runnable runnable, boolean inputViewStarted, boolean runOnMainSync)
+ throws InterruptedException {
+ CountDownLatch signal = new CountDownLatch(1);
+ mInputMethodService.setCountDownLatchForTesting(signal);
+ // Runnable to trigger onStartInputView()/ onFinishInputView()
+ if (runOnMainSync) {
+ mInstrumentation.runOnMainSync(runnable);
+ } else {
+ runnable.run();
+ }
+ // Waits for onStartInputView() to finish.
+ mInstrumentation.waitForIdleSync();
+ signal.await(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS);
+ // Input is not finished.
+ assertThat(mInputMethodService.getCurrentInputStarted()).isTrue();
+ assertThat(mInputMethodService.getCurrentInputViewStarted()).isEqualTo(inputViewStarted);
+ }
+
+ @Test
+ public void testFullScreenMode() throws Exception {
+ Log.i(TAG, "Set orientation natural");
+ verifyFullscreenMode(() -> setOrientation(0), true /* orientationPortrait */);
+
+ Log.i(TAG, "Set orientation left");
+ verifyFullscreenMode(() -> setOrientation(1), false /* orientationPortrait */);
+
+ Log.i(TAG, "Set orientation right");
+ verifyFullscreenMode(() -> setOrientation(2), false /* orientationPortrait */);
+
+ mUiDevice.unfreezeRotation();
+ }
+
+ private void setOrientation(int orientation) {
+ // Simple wrapper for catching RemoteException.
+ try {
+ switch (orientation) {
+ case 1:
+ mUiDevice.setOrientationLeft();
+ break;
+ case 2:
+ mUiDevice.setOrientationRight();
+ break;
+ default:
+ mUiDevice.setOrientationNatural();
+ }
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private void verifyFullscreenMode(Runnable runnable, boolean orientationPortrait)
+ throws InterruptedException {
+ CountDownLatch signal = new CountDownLatch(1);
+ mInputMethodService.setCountDownLatchForTesting(signal);
+
+ // Runnable to trigger onConfigurationChanged()
+ try {
+ runnable.run();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ // Waits for onConfigurationChanged() to finish.
+ mInstrumentation.waitForIdleSync();
+ signal.await(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS);
+
+ clickOnEditorText();
+ eventually(() -> assertThat(mInputMethodService.isInputViewShown()).isTrue());
+
+ assertThat(mInputMethodService.getResources().getConfiguration().orientation)
+ .isEqualTo(
+ orientationPortrait
+ ? Configuration.ORIENTATION_PORTRAIT
+ : Configuration.ORIENTATION_LANDSCAPE);
+ EditorInfo editorInfo = mInputMethodService.getCurrentInputEditorInfo();
+ assertThat(editorInfo.imeOptions & EditorInfo.IME_FLAG_NO_FULLSCREEN).isEqualTo(0);
+ assertThat(editorInfo.internalImeOptions & EditorInfo.IME_INTERNAL_FLAG_APP_WINDOW_PORTRAIT)
+ .isEqualTo(
+ orientationPortrait ? EditorInfo.IME_INTERNAL_FLAG_APP_WINDOW_PORTRAIT : 0);
+ assertThat(mInputMethodService.onEvaluateFullscreenMode()).isEqualTo(!orientationPortrait);
+ assertThat(mInputMethodService.isFullscreenMode()).isEqualTo(!orientationPortrait);
+
+ mUiDevice.pressBack();
+ }
+
+ private void prepareIme() throws Exception {
+ executeShellCommand("ime enable " + mInputMethodId);
+ executeShellCommand("ime set " + mInputMethodId);
+ mInstrumentation.waitForIdleSync();
+ Log.i(TAG, "Finish preparing IME");
+ }
+
+ private void prepareEditor() {
+ mActivity = TestActivity.start(mInstrumentation);
+ mEditText = mActivity.mEditText;
+ Log.i(TAG, "Finish preparing activity with editor.");
+ }
+
+ private String getInputMethodId() {
+ return mTargetPackageName + "/" + INPUT_METHOD_SERVICE_NAME;
+ }
+
+ private String executeShellCommand(String cmd) throws Exception {
+ Log.i(TAG, "Run command: " + cmd);
+ return UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
+ .executeShellCommand(cmd);
+ }
+
+ private void clickOnEditorText() {
+ // Find the editText and click it.
+ UiObject2 editTextUiObject =
+ mUiDevice.wait(
+ Until.findObject(By.desc(EDIT_TEXT_DESC)),
+ TimeUnit.SECONDS.toMillis(TIMEOUT_IN_SECONDS));
+ assertThat(editTextUiObject).isNotNull();
+ editTextUiObject.click();
+ mInstrumentation.waitForIdleSync();
+ }
+}
diff --git a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/Android.bp b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/Android.bp
new file mode 100644
index 000000000000..ef50476c2928
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/Android.bp
@@ -0,0 +1,62 @@
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test_helper_app {
+ name: "SimpleTestIme",
+
+ srcs: [
+ "src/com/android/apps/inputmethod/simpleime/*.java",
+ ],
+
+ static_libs: [
+ "SimpleImeImsLib",
+ "SimpleImeTestingLib",
+ ],
+ resource_dirs: ["res"],
+ manifest: "AndroidManifest.xml",
+
+ dex_preopt: {
+ enabled: false,
+ },
+ optimize: {
+ enabled: false,
+ },
+ export_package_resources: true,
+ sdk_version: "current",
+}
+
+android_library {
+ name: "SimpleImeImsLib",
+ srcs: [
+ "src/com/android/apps/inputmethod/simpleime/ims/*.java",
+ ],
+ sdk_version: "current",
+}
+
+android_library {
+ name: "SimpleImeTestingLib",
+ srcs: [
+ "src/com/android/apps/inputmethod/simpleime/testing/*.java",
+ ],
+ sdk_version: "current",
+}
diff --git a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/AndroidManifest.xml b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/AndroidManifest.xml
new file mode 100644
index 000000000000..802caf132db5
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/AndroidManifest.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.apps.inputmethod.simpleime">
+
+ <uses-sdk android:targetSdkVersion="31" />
+
+ <application android:debuggable="true"
+ android:label="@string/app_name">
+ <service
+ android:name="com.android.apps.inputmethod.simpleime.SimpleInputMethodService"
+ android:label="@string/app_name"
+ android:directBootAware="true"
+ android:permission="android.permission.BIND_INPUT_METHOD"
+ android:exported="true">
+
+ <meta-data
+ android:name="android.view.im"
+ android:resource="@xml/method"/>
+
+ <intent-filter>
+ <action android:name="android.view.InputMethod"/>
+ </intent-filter>
+ </service>
+
+ <!-- This is for test only. -->
+ <activity android:name="com.android.apps.inputmethod.simpleime.testing.TestActivity"
+ android:exported="false"
+ android:label="TestActivity"
+ android:launchMode="singleInstance"
+ android:excludeFromRecents="true"
+ android:noHistory="true"
+ android:taskAffinity="">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest> \ No newline at end of file
diff --git a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/res/drawable/key_border.xml b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/res/drawable/key_border.xml
new file mode 100644
index 000000000000..dbfcc30a7f4e
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/res/drawable/key_border.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle" >
+ <solid
+ android:color="#FAFAFA" >
+ </solid>
+ <stroke
+ android:width="1dp"
+ android:color="#0F000000" >
+ </stroke>
+ <corners
+ android:radius="2dp" >
+ </corners>
+</shape> \ No newline at end of file
diff --git a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/res/layout/input_view.xml b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/res/layout/input_view.xml
new file mode 100644
index 000000000000..f2292701e795
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/res/layout/input_view.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/input"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"/> \ No newline at end of file
diff --git a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/res/layout/qwerty_10_9_9.xml b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/res/layout/qwerty_10_9_9.xml
new file mode 100644
index 000000000000..ee94ea9eee1e
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/res/layout/qwerty_10_9_9.xml
@@ -0,0 +1,200 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ style="@style/KeyboardArea">
+
+ <View style="@style/KeyboardRow.Header"/>
+
+ <LinearLayout style="@style/KeyboardRow">
+ <TextView
+ android:id="@+id/key_pos_0_0"
+ android:text="q"
+ android:tag="KEYCODE_Q"
+ style="@style/SoftKey"/>
+ <TextView
+ android:id="@+id/key_pos_0_1"
+ android:text="w"
+ android:tag="KEYCODE_W"
+ style="@style/SoftKey"/>
+ <TextView
+ android:id="@+id/key_pos_0_2"
+ android:text="e"
+ android:tag="KEYCODE_E"
+ style="@style/SoftKey"/>
+ <TextView
+ android:id="@+id/key_pos_0_3"
+ android:text="r"
+ android:tag="KEYCODE_R"
+ style="@style/SoftKey"/>
+ <TextView
+ android:id="@+id/key_pos_0_4"
+ android:text="t"
+ android:tag="KEYCODE_T"
+ style="@style/SoftKey"/>
+ <TextView
+ android:id="@+id/key_pos_0_5"
+ android:text="y"
+ android:tag="KEYCODE_Y"
+ style="@style/SoftKey"/>
+ <TextView
+ android:id="@+id/key_pos_0_6"
+ android:text="u"
+ android:tag="KEYCODE_U"
+ style="@style/SoftKey"/>
+ <TextView
+ android:id="@+id/key_pos_0_7"
+ android:text="i"
+ android:tag="KEYCODE_I"
+ style="@style/SoftKey"/>
+ <TextView
+ android:id="@+id/key_pos_0_8"
+ android:text="o"
+ android:tag="KEYCODE_O"
+ style="@style/SoftKey"/>
+ <TextView
+ android:id="@+id/key_pos_0_9"
+ android:text="p"
+ android:tag="KEYCODE_P"
+ style="@style/SoftKey"/>
+ </LinearLayout>
+
+ <LinearLayout style="@style/KeyboardRow">
+ <TextView
+ android:id="@+id/key_pos_1_0"
+ android:text="a"
+ android:tag="KEYCODE_A"
+ style="@style/SoftKey"/>
+ <TextView
+ android:id="@+id/key_pos_1_1"
+ android:text="s"
+ android:tag="KEYCODE_S"
+ style="@style/SoftKey"/>
+ <TextView
+ android:id="@+id/key_pos_1_2"
+ android:text="d"
+ android:tag="KEYCODE_D"
+ style="@style/SoftKey"/>
+ <TextView
+ android:id="@+id/key_pos_1_3"
+ android:text="f"
+ android:tag="KEYCODE_F"
+ style="@style/SoftKey"/>
+ <TextView
+ android:id="@+id/key_pos_1_4"
+ android:text="g"
+ android:tag="KEYCODE_G"
+ style="@style/SoftKey"/>
+ <TextView
+ android:id="@+id/key_pos_1_5"
+ android:text="h"
+ android:tag="KEYCODE_H"
+ style="@style/SoftKey"/>
+ <TextView
+ android:id="@+id/key_pos_1_6"
+ android:text="j"
+ android:tag="KEYCODE_J"
+ style="@style/SoftKey"/>
+ <TextView
+ android:id="@+id/key_pos_1_7"
+ android:text="k"
+ android:tag="KEYCODE_K"
+ style="@style/SoftKey"/>
+ <TextView
+ android:id="@+id/key_pos_1_8"
+ android:text="l"
+ android:tag="KEYCODE_L"
+ style="@style/SoftKey"/>
+ </LinearLayout>
+
+ <LinearLayout style="@style/KeyboardRow">
+ <TextView
+ android:id="@+id/key_pos_shift"
+ android:text="SHI"
+ android:tag="KEYCODE_SHIFT"
+ style="@style/SoftKey.Function"/>
+ <TextView
+ android:id="@+id/key_pos_2_0"
+ android:text="z"
+ android:tag="KEYCODE_Z"
+ style="@style/SoftKey"/>
+ <TextView
+ android:id="@+id/key_pos_2_1"
+ android:text="x"
+ android:tag="KEYCODE_X"
+ style="@style/SoftKey"/>
+ <TextView
+ android:id="@+id/key_pos_2_2"
+ style="@style/SoftKey"
+ android:text="c"
+ android:tag="KEYCODE_C"/>
+ <TextView
+ android:id="@+id/key_pos_2_3"
+ android:text="v"
+ android:tag="KEYCODE_V"
+ style="@style/SoftKey"/>
+ <TextView
+ android:id="@+id/key_pos_2_4"
+ android:text="b"
+ android:tag="KEYCODE_B"
+ style="@style/SoftKey"/>
+ <TextView
+ android:id="@+id/key_pos_2_5"
+ android:text="n"
+ android:tag="KEYCODE_N"
+ style="@style/SoftKey"/>
+ <TextView
+ android:id="@+id/key_pos_2_6"
+ android:text="m"
+ android:tag="KEYCODE_M"
+ style="@style/SoftKey"/>
+ <TextView
+ android:id="@+id/key_pos_del"
+ android:text="DEL"
+ android:tag="KEYCODE_DEL"
+ style="@style/SoftKey.Function"/>
+ </LinearLayout>
+
+ <LinearLayout style="@style/KeyboardRow">
+ <TextView
+ android:id="@+id/key_pos_symbol"
+ android:text="TAB"
+ android:tag="KEYCODE_TAB"
+ style="@style/SoftKey.Function"/>
+ <TextView
+ android:id="@+id/key_pos_comma"
+ android:text=","
+ android:tag="KEYCODE_COMMA"
+ style="@style/SoftKey"/>
+ <TextView
+ android:id="@+id/key_pos_space"
+ android:text="SPACE"
+ android:tag="KEYCODE_SPACE"
+ style="@style/SoftKey.Space"/>
+ <TextView
+ android:id="@+id/key_pos_period"
+ android:text="."
+ android:tag="KEYCODE_PERIOD"
+ style="@style/SoftKey"/>
+ <TextView
+ android:id="@+id/key_pos_enter"
+ android:text="ENT"
+ android:tag="KEYCODE_ENTER"
+ style="@style/SoftKey.Function.Bottom"/>
+ </LinearLayout>
+</LinearLayout>
diff --git a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/res/values/dimens.xml b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/res/values/dimens.xml
new file mode 100644
index 000000000000..1a4959e14a1f
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/res/values/dimens.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources>
+ <dimen name="text_size_normal">24dp</dimen>
+ <dimen name="text_size_symbol">14dp</dimen>
+
+ <dimen name="keyboard_header_height">40dp</dimen>
+ <dimen name="keyboard_row_height">50dp</dimen>
+</resources> \ No newline at end of file
diff --git a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/res/values/strings.xml b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/res/values/strings.xml
new file mode 100644
index 000000000000..11377fa02dc9
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/res/values/strings.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources>
+ <string name="app_name">Fake IME</string>
+</resources> \ No newline at end of file
diff --git a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/res/values/styles.xml b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/res/values/styles.xml
new file mode 100644
index 000000000000..83f7bc317066
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/res/values/styles.xml
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources>
+ <style name="KeyboardArea">
+ <item name="android:layout_width">match_parent</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:layout_gravity">bottom</item>
+ <item name="android:orientation">vertical</item>
+ <item name="android:background">#FFFFFFFF</item>
+ </style>
+
+ <style name="KeyboardRow">
+ <item name="android:layout_width">match_parent</item>
+ <item name="android:layout_height">@dimen/keyboard_row_height</item>
+ <item name="android:orientation">horizontal</item>
+ </style>
+
+ <style name="KeyboardRow.Header">
+ <item name="android:layout_height">@dimen/keyboard_header_height</item>
+ <item name="android:background">#FFEEEEEE</item>
+ </style>
+
+ <style name="SoftKey">
+ <item name="android:layout_width">0dp</item>
+ <item name="android:layout_height">match_parent</item>
+ <item name="android:layout_weight">2</item>
+ <item name="android:gravity">center</item>
+ <item name="android:textColor">#FF000000</item>
+ <item name="android:textSize">@dimen/text_size_normal</item>
+ <item name="android:fontFamily">roboto-regular</item>
+ <item name="android:background">@drawable/key_border</item>
+ </style>
+
+ <style name="SoftKey.Function">
+ <item name="android:layout_weight">3</item>
+ <item name="android:textColor">#FF333333</item>
+ <item name="android:textSize">@dimen/text_size_symbol</item>
+ </style>
+
+ <style name="SoftKey.Function.Bottom">
+ <item name="android:layout_weight">3</item>
+ <item name="android:textColor">#FF333333</item>
+ <item name="android:textSize">@dimen/text_size_symbol</item>
+ </style>
+
+ <style name="SoftKey.Space">
+ <item name="android:layout_weight">10</item>
+ <item name="android:textColor">#FF333333</item>
+ <item name="android:textSize">@dimen/text_size_symbol</item>
+ </style>
+</resources> \ No newline at end of file
diff --git a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/res/xml/method.xml b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/res/xml/method.xml
new file mode 100644
index 000000000000..872b0688814a
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/res/xml/method.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<input-method xmlns:android="http://schemas.android.com/apk/res/android">
+ <subtype
+ android:label="FakeIme"
+ android:imeSubtypeLocale="en_US"
+ android:imeSubtypeMode="keyboard"/>
+</input-method> \ No newline at end of file
diff --git a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/KeyCodeConstants.java b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/KeyCodeConstants.java
new file mode 100644
index 000000000000..990fa24aad22
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/KeyCodeConstants.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.apps.inputmethod.simpleime;
+
+import android.view.KeyEvent;
+
+import java.util.HashMap;
+
+/** Holder of key codes and their name. */
+public final class KeyCodeConstants {
+ private KeyCodeConstants() {}
+
+ static final HashMap<String, Integer> KEY_NAME_TO_CODE_MAP = new HashMap<>();
+
+ static {
+ KEY_NAME_TO_CODE_MAP.put("KEYCODE_A", KeyEvent.KEYCODE_A);
+ KEY_NAME_TO_CODE_MAP.put("KEYCODE_B", KeyEvent.KEYCODE_B);
+ KEY_NAME_TO_CODE_MAP.put("KEYCODE_C", KeyEvent.KEYCODE_C);
+ KEY_NAME_TO_CODE_MAP.put("KEYCODE_D", KeyEvent.KEYCODE_D);
+ KEY_NAME_TO_CODE_MAP.put("KEYCODE_E", KeyEvent.KEYCODE_E);
+ KEY_NAME_TO_CODE_MAP.put("KEYCODE_F", KeyEvent.KEYCODE_F);
+ KEY_NAME_TO_CODE_MAP.put("KEYCODE_G", KeyEvent.KEYCODE_G);
+ KEY_NAME_TO_CODE_MAP.put("KEYCODE_H", KeyEvent.KEYCODE_H);
+ KEY_NAME_TO_CODE_MAP.put("KEYCODE_I", KeyEvent.KEYCODE_I);
+ KEY_NAME_TO_CODE_MAP.put("KEYCODE_J", KeyEvent.KEYCODE_J);
+ KEY_NAME_TO_CODE_MAP.put("KEYCODE_K", KeyEvent.KEYCODE_K);
+ KEY_NAME_TO_CODE_MAP.put("KEYCODE_L", KeyEvent.KEYCODE_L);
+ KEY_NAME_TO_CODE_MAP.put("KEYCODE_M", KeyEvent.KEYCODE_M);
+ KEY_NAME_TO_CODE_MAP.put("KEYCODE_N", KeyEvent.KEYCODE_N);
+ KEY_NAME_TO_CODE_MAP.put("KEYCODE_O", KeyEvent.KEYCODE_O);
+ KEY_NAME_TO_CODE_MAP.put("KEYCODE_P", KeyEvent.KEYCODE_P);
+ KEY_NAME_TO_CODE_MAP.put("KEYCODE_Q", KeyEvent.KEYCODE_Q);
+ KEY_NAME_TO_CODE_MAP.put("KEYCODE_R", KeyEvent.KEYCODE_R);
+ KEY_NAME_TO_CODE_MAP.put("KEYCODE_S", KeyEvent.KEYCODE_S);
+ KEY_NAME_TO_CODE_MAP.put("KEYCODE_T", KeyEvent.KEYCODE_T);
+ KEY_NAME_TO_CODE_MAP.put("KEYCODE_U", KeyEvent.KEYCODE_U);
+ KEY_NAME_TO_CODE_MAP.put("KEYCODE_V", KeyEvent.KEYCODE_V);
+ KEY_NAME_TO_CODE_MAP.put("KEYCODE_W", KeyEvent.KEYCODE_W);
+ KEY_NAME_TO_CODE_MAP.put("KEYCODE_X", KeyEvent.KEYCODE_X);
+ KEY_NAME_TO_CODE_MAP.put("KEYCODE_Y", KeyEvent.KEYCODE_Y);
+ KEY_NAME_TO_CODE_MAP.put("KEYCODE_Z", KeyEvent.KEYCODE_Z);
+ KEY_NAME_TO_CODE_MAP.put("KEYCODE_SHIFT", KeyEvent.KEYCODE_SHIFT_LEFT);
+ KEY_NAME_TO_CODE_MAP.put("KEYCODE_DEL", KeyEvent.KEYCODE_DEL);
+ KEY_NAME_TO_CODE_MAP.put("KEYCODE_SPACE", KeyEvent.KEYCODE_SPACE);
+ KEY_NAME_TO_CODE_MAP.put("KEYCODE_ENTER", KeyEvent.KEYCODE_ENTER);
+ KEY_NAME_TO_CODE_MAP.put("KEYCODE_COMMA", KeyEvent.KEYCODE_COMMA);
+ KEY_NAME_TO_CODE_MAP.put("KEYCODE_PERIOD", KeyEvent.KEYCODE_PERIOD);
+ KEY_NAME_TO_CODE_MAP.put("KEYCODE_TAB", KeyEvent.KEYCODE_TAB);
+ }
+
+ public static boolean isAlphaKeyCode(int keyCode) {
+ return keyCode >= KeyEvent.KEYCODE_A && keyCode <= KeyEvent.KEYCODE_Z;
+ }
+}
diff --git a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/SimpleInputMethodService.java b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/SimpleInputMethodService.java
new file mode 100644
index 000000000000..48942a31658f
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/SimpleInputMethodService.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.apps.inputmethod.simpleime;
+
+import android.inputmethodservice.InputMethodService;
+import android.os.SystemClock;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
+import android.widget.FrameLayout;
+
+import com.android.apps.inputmethod.simpleime.ims.InputMethodServiceWrapper;
+
+/** The {@link InputMethodService} implementation for SimpeTestIme app. */
+public class SimpleInputMethodService extends InputMethodServiceWrapper {
+ private static final String TAG = "SimpleIMS";
+
+ private FrameLayout mInputView;
+
+ @Override
+ public View onCreateInputView() {
+ Log.i(TAG, "onCreateInputView()");
+ mInputView = (FrameLayout) LayoutInflater.from(this).inflate(R.layout.input_view, null);
+ return mInputView;
+ }
+
+ @Override
+ public void onStartInputView(EditorInfo info, boolean restarting) {
+ super.onStartInputView(info, restarting);
+ mInputView.removeAllViews();
+ SimpleKeyboard keyboard = new SimpleKeyboard(this, R.layout.qwerty_10_9_9);
+ mInputView.addView(keyboard.inflateKeyboardView(LayoutInflater.from(this), mInputView));
+ }
+
+ void handle(String data, int keyboardState) {
+ InputConnection inputConnection = getCurrentInputConnection();
+ Integer keyCode = KeyCodeConstants.KEY_NAME_TO_CODE_MAP.get(data);
+ Log.v(TAG, "keyCode: " + keyCode);
+ if (keyCode != null) {
+ inputConnection.sendKeyEvent(
+ new KeyEvent(
+ SystemClock.uptimeMillis(),
+ SystemClock.uptimeMillis(),
+ KeyEvent.ACTION_DOWN,
+ keyCode,
+ 0,
+ KeyCodeConstants.isAlphaKeyCode(keyCode) ? keyboardState : 0));
+ }
+ }
+}
diff --git a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/SimpleKeyboard.java b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/SimpleKeyboard.java
new file mode 100644
index 000000000000..b16ec9ebd718
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/SimpleKeyboard.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.apps.inputmethod.simpleime;
+
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+/** Controls the visible virtual keyboard view. */
+final class SimpleKeyboard {
+ private static final String TAG = "SimpleKeyboard";
+
+ private static final int[] SOFT_KEY_IDS =
+ new int[] {
+ R.id.key_pos_0_0,
+ R.id.key_pos_0_1,
+ R.id.key_pos_0_2,
+ R.id.key_pos_0_3,
+ R.id.key_pos_0_4,
+ R.id.key_pos_0_5,
+ R.id.key_pos_0_6,
+ R.id.key_pos_0_7,
+ R.id.key_pos_0_8,
+ R.id.key_pos_0_9,
+ R.id.key_pos_1_0,
+ R.id.key_pos_1_1,
+ R.id.key_pos_1_2,
+ R.id.key_pos_1_3,
+ R.id.key_pos_1_4,
+ R.id.key_pos_1_5,
+ R.id.key_pos_1_6,
+ R.id.key_pos_1_7,
+ R.id.key_pos_1_8,
+ R.id.key_pos_2_0,
+ R.id.key_pos_2_1,
+ R.id.key_pos_2_2,
+ R.id.key_pos_2_3,
+ R.id.key_pos_2_4,
+ R.id.key_pos_2_5,
+ R.id.key_pos_2_6,
+ R.id.key_pos_shift,
+ R.id.key_pos_del,
+ R.id.key_pos_symbol,
+ R.id.key_pos_comma,
+ R.id.key_pos_space,
+ R.id.key_pos_period,
+ R.id.key_pos_enter,
+ };
+
+ private final SimpleInputMethodService mSimpleInputMethodService;
+ private final int mViewResId;
+ private final SparseArray<TextView> mSoftKeyViews = new SparseArray<>();
+ private View mKeyboardView;
+ private int mKeyboardState;
+
+ SimpleKeyboard(SimpleInputMethodService simpleInputMethodService, int viewResId) {
+ this.mSimpleInputMethodService = simpleInputMethodService;
+ this.mViewResId = viewResId;
+ this.mKeyboardState = 0;
+ }
+
+ View inflateKeyboardView(LayoutInflater inflater, ViewGroup inputView) {
+ mKeyboardView = inflater.inflate(mViewResId, inputView, false);
+ mapSoftKeys();
+ return mKeyboardView;
+ }
+
+ private void mapSoftKeys() {
+ for (int id : SOFT_KEY_IDS) {
+ TextView softKeyView = mKeyboardView.findViewById(id);
+ mSoftKeyViews.put(id, softKeyView);
+ String tagData = softKeyView.getTag() != null ? softKeyView.getTag().toString() : null;
+ softKeyView.setOnClickListener(v -> handle(tagData));
+ }
+ }
+
+ private void handle(String data) {
+ Log.i(TAG, "handle(): " + data);
+ if (TextUtils.isEmpty(data)) {
+ return;
+ }
+ if ("KEYCODE_SHIFT".equals(data)) {
+ handleShift();
+ return;
+ }
+
+ mSimpleInputMethodService.handle(data, mKeyboardState);
+ }
+
+ private void handleShift() {
+ mKeyboardState = toggleShiftState(mKeyboardState);
+ Log.v(TAG, "currentKeyboardState: " + mKeyboardState);
+ boolean isShiftOn = isShiftOn(mKeyboardState);
+ for (int i = 0; i < mSoftKeyViews.size(); i++) {
+ TextView softKeyView = mSoftKeyViews.valueAt(i);
+ softKeyView.setAllCaps(isShiftOn);
+ }
+ }
+
+ private static boolean isShiftOn(int state) {
+ return (state & KeyEvent.META_SHIFT_ON) == KeyEvent.META_SHIFT_ON;
+ }
+
+ private static int toggleShiftState(int state) {
+ return state ^ KeyEvent.META_SHIFT_ON;
+ }
+}
diff --git a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/ims/InputMethodServiceWrapper.java b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/ims/InputMethodServiceWrapper.java
new file mode 100644
index 000000000000..b706a654e3a8
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/ims/InputMethodServiceWrapper.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.apps.inputmethod.simpleime.ims;
+
+import android.content.res.Configuration;
+import android.inputmethodservice.InputMethodService;
+import android.util.Log;
+import android.view.inputmethod.EditorInfo;
+
+import java.util.concurrent.CountDownLatch;
+
+/** Wrapper of {@link InputMethodService} to expose interfaces for testing purpose. */
+public class InputMethodServiceWrapper extends InputMethodService {
+ private static final String TAG = "InputMethodServiceWrapper";
+
+ private static InputMethodServiceWrapper sInputMethodServiceWrapper;
+
+ public static InputMethodServiceWrapper getInputMethodServiceWrapperForTesting() {
+ return sInputMethodServiceWrapper;
+ }
+
+ private boolean mInputViewStarted;
+ private CountDownLatch mCountDownLatchForTesting;
+
+ public boolean getCurrentInputViewStarted() {
+ return mInputViewStarted;
+ }
+
+ public void setCountDownLatchForTesting(CountDownLatch countDownLatchForTesting) {
+ mCountDownLatchForTesting = countDownLatchForTesting;
+ }
+
+ @Override
+ public void onCreate() {
+ Log.i(TAG, "onCreate()");
+ super.onCreate();
+ sInputMethodServiceWrapper = this;
+ }
+
+ @Override
+ public void onStartInput(EditorInfo info, boolean restarting) {
+ Log.i(TAG, "onStartInput() editor=" + info + ", restarting=" + restarting);
+ super.onStartInput(info, restarting);
+ }
+
+ @Override
+ public void onStartInputView(EditorInfo info, boolean restarting) {
+ Log.i(TAG, "onStartInputView() editor=" + info + ", restarting=" + restarting);
+ super.onStartInputView(info, restarting);
+ mInputViewStarted = true;
+ if (mCountDownLatchForTesting != null) {
+ mCountDownLatchForTesting.countDown();
+ }
+ }
+
+ @Override
+ public void onFinishInput() {
+ Log.i(TAG, "onFinishInput()");
+ super.onFinishInput();
+ }
+
+ @Override
+ public void onFinishInputView(boolean finishingInput) {
+ Log.i(TAG, "onFinishInputView()");
+ super.onFinishInputView(finishingInput);
+ mInputViewStarted = false;
+
+ if (mCountDownLatchForTesting != null) {
+ mCountDownLatchForTesting.countDown();
+ }
+ }
+
+ @Override
+ public void requestHideSelf(int flags) {
+ Log.i(TAG, "requestHideSelf() " + flags);
+ super.requestHideSelf(flags);
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ Log.i(TAG, "onConfigurationChanged() " + newConfig);
+ super.onConfigurationChanged(newConfig);
+
+ if (mCountDownLatchForTesting != null) {
+ mCountDownLatchForTesting.countDown();
+ }
+ }
+}
diff --git a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/testing/TestActivity.java b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/testing/TestActivity.java
new file mode 100644
index 000000000000..0eec7e629d87
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/testing/TestActivity.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.apps.inputmethod.simpleime.testing;
+
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.WindowInsets;
+import android.view.WindowInsetsController;
+import android.view.WindowManager;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+
+/**
+ * A special activity for testing purpose.
+ *
+ * <p>This is used when the instruments package is SimpleTestIme, as the Intent needs to be started
+ * in the instruments package. More details see {@link
+ * Instrumentation#startActivitySync(Intent)}.</>
+ */
+public class TestActivity extends Activity {
+ private static final String TAG = "TestActivity";
+
+ /**
+ * Start a new test activity with an editor and wait for it to begin running before returning.
+ *
+ * @param instrumentation application instrumentation
+ * @return the newly started activity
+ */
+ public static TestActivity start(Instrumentation instrumentation) {
+ Intent intent =
+ new Intent()
+ .setAction(Intent.ACTION_MAIN)
+ .setClass(instrumentation.getTargetContext(), TestActivity.class)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ return (TestActivity) instrumentation.startActivitySync(intent);
+ }
+
+ public EditText mEditText;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
+ LinearLayout rootView = new LinearLayout(this);
+ mEditText = new EditText(this);
+ mEditText.setContentDescription("Input box");
+ rootView.addView(mEditText, new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT));
+ setContentView(rootView);
+ mEditText.requestFocus();
+ super.onCreate(savedInstanceState);
+ }
+
+ /** Shows soft keyboard via InputMethodManager. */
+ public boolean showImeWithInputMethodManager(int flags) {
+ InputMethodManager imm = getSystemService(InputMethodManager.class);
+ boolean result = imm.showSoftInput(mEditText, flags);
+ Log.i(TAG, "hideIme() via InputMethodManager, result=" + result);
+ return result;
+ }
+
+ /** Shows soft keyboard via WindowInsetsController. */
+ public boolean showImeWithWindowInsetsController() {
+ WindowInsetsController windowInsetsController = mEditText.getWindowInsetsController();
+ windowInsetsController.show(WindowInsets.Type.ime());
+ Log.i(TAG, "showIme() via WindowInsetsController");
+ return true;
+ }
+
+ /** Hides soft keyboard via InputMethodManager. */
+ public boolean hideImeWithInputMethodManager(int flags) {
+ InputMethodManager imm = getSystemService(InputMethodManager.class);
+ boolean result = imm.hideSoftInputFromWindow(mEditText.getWindowToken(), flags);
+ Log.i(TAG, "hideIme() via InputMethodManager, result=" + result);
+ return result;
+ }
+
+ /** Hides soft keyboard via WindowInsetsController. */
+ public boolean hideImeWithWindowInsetsController() {
+ WindowInsetsController windowInsetsController = mEditText.getWindowInsetsController();
+ windowInsetsController.hide(WindowInsets.Type.ime());
+ Log.i(TAG, "hideIme() via WindowInsetsController");
+ return true;
+ }
+}
diff --git a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/OverlayPathsUninstallSystemUpdatesTest.kt b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/OverlayPathsUninstallSystemUpdatesTest.kt
new file mode 100644
index 000000000000..404478023226
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/OverlayPathsUninstallSystemUpdatesTest.kt
@@ -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 com.android.server.pm.test
+
+import com.android.internal.util.test.SystemPreparer
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.ClassRule
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.RuleChain
+import org.junit.rules.TemporaryFolder
+import org.junit.runner.RunWith
+
+@RunWith(DeviceJUnit4ClassRunner::class)
+class OverlayPathsUninstallSystemUpdatesTest : BaseHostJUnit4Test() {
+
+ companion object {
+ private const val TEST_PKG_NAME = "com.android.server.pm.test.test_app"
+ private const val VERSION_ONE = "PackageManagerTestAppVersion1.apk"
+ private const val VERSION_TWO = "PackageManagerTestAppVersion2.apk"
+
+ @get:ClassRule
+ val deviceRebootRule = SystemPreparer.TestRuleDelegate(true)
+ }
+
+ private val tempFolder = TemporaryFolder()
+ private val preparer: SystemPreparer = SystemPreparer(tempFolder,
+ SystemPreparer.RebootStrategy.FULL, deviceRebootRule, true) { this.device }
+
+ @Rule
+ @JvmField
+ val rules = RuleChain.outerRule(tempFolder).around(preparer)!!
+ private val filePath = HostUtils.makePathForApk("PackageManagerTestApp.apk", Partition.PRODUCT)
+
+ @Before
+ @After
+ fun removeApk() {
+ device.uninstallPackage(TEST_PKG_NAME)
+ }
+
+ @Test
+ fun verify() {
+ // First, push a system app to the device and then update it so there's a data variant
+ preparer.pushResourceFile(VERSION_ONE, filePath.toString())
+ .reboot()
+
+ val versionTwoFile = HostUtils.copyResourceToHostFile(VERSION_TWO, tempFolder.newFile())
+
+ assertThat(device.installPackage(versionTwoFile, true)).isNull()
+
+ device.executeShellCommand(
+ "cmd overlay fabricate --target-name TestResources" +
+ " --target $TEST_PKG_NAME" +
+ " --name UninstallSystemUpdatesTest" +
+ " $TEST_PKG_NAME:color/overlay_test 0x1C 0xFFFFFFFF"
+ )
+
+ device.executeShellCommand(
+ "cmd overlay enable --user 0 com.android.shell:UninstallSystemUpdatesTest"
+ )
+
+ fun verifyValueOverlaid() {
+ assertThat(device.executeShellCommand(
+ "cmd overlay lookup --user 0 $TEST_PKG_NAME $TEST_PKG_NAME:color/overlay_test"
+ ).trim()).isEqualTo("#ffffffff")
+ }
+
+ verifyValueOverlaid()
+
+ assertThat(
+ device.executeShellCommand("pm uninstall-system-updates $TEST_PKG_NAME"
+ ).trim()).endsWith("Success")
+
+ // Wait for paths to re-propagate. This doesn't do a retry loop in case the path clear also
+ // has some latency. There must be some minimum wait time for the paths to settle, and then
+ // a wait time for the paths to re-propagate. Rather than complicate the logic, just wait
+ // a long enough time for both events to occur.
+ Thread.sleep(5000)
+
+ verifyValueOverlaid()
+ }
+}
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/Generic/res/values/colors.xml b/services/tests/PackageManagerServiceTests/host/test-apps/Generic/res/values/colors.xml
new file mode 100644
index 000000000000..5a41d886cc8f
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/Generic/res/values/colors.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<resources>
+ <color name="overlay_test">#FF000000</color>
+</resources>
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/Generic/res/values/overlayable.xml b/services/tests/PackageManagerServiceTests/host/test-apps/Generic/res/values/overlayable.xml
new file mode 100644
index 000000000000..c5ba450156c2
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/Generic/res/values/overlayable.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<resources>
+ <overlayable name="TestResources">
+ <policy type="public">
+ <item type="color" name="overlay_test" />
+ </policy>
+ </overlayable>
+</resources>
diff --git a/services/tests/PackageManagerServiceTests/server/Android.bp b/services/tests/PackageManagerServiceTests/server/Android.bp
index ebd6b649c4a8..cc2659308a95 100644
--- a/services/tests/PackageManagerServiceTests/server/Android.bp
+++ b/services/tests/PackageManagerServiceTests/server/Android.bp
@@ -94,6 +94,7 @@ android_test {
"libunwindstack",
"libutils",
"netd_aidl_interface-V5-cpp",
+ "libservices.core.settings.testonly",
],
dxflags: ["--multi-dex"],
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageManagerSettingsTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageManagerSettingsTests.java
index 3727d660f9a7..b3f64b6db54c 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageManagerSettingsTests.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageManagerSettingsTests.java
@@ -111,6 +111,10 @@ public class PackageManagerSettingsTests {
private static final String PACKAGE_NAME_3 = "com.android.app3";
private static final int TEST_RESOURCE_ID = 2131231283;
+ static {
+ System.loadLibrary("services.core.settings.testonly");
+ }
+
@Mock
RuntimePermissionsPersistence mRuntimePermissionsPersistence;
@Mock
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageParserTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageParserTest.java
index 59f27ec2766e..c8e26766646f 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageParserTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageParserTest.java
@@ -334,7 +334,7 @@ public class PackageParserTest {
}
@Test
- public void testParseActivityTargetDisplayCategoryValid() throws Exception {
+ public void testParseActivityRequiredDisplayCategoryValid() throws Exception {
final File testFile = extractFile(TEST_APP4_APK);
String actualDisplayCategory = null;
try {
@@ -342,7 +342,7 @@ public class PackageParserTest {
final List<ParsedActivity> activities = pkg.getActivities();
for (ParsedActivity activity : activities) {
if ((PACKAGE_NAME + ".MyActivity").equals(activity.getName())) {
- actualDisplayCategory = activity.getTargetDisplayCategory();
+ actualDisplayCategory = activity.getRequiredDisplayCategory();
}
}
} finally {
@@ -352,7 +352,7 @@ public class PackageParserTest {
}
@Test
- public void testParseActivityTargetDisplayCategoryInvalid() throws Exception {
+ public void testParseActivityRequiredDisplayCategoryInvalid() throws Exception {
final File testFile = extractFile(TEST_APP6_APK);
String actualDisplayCategory = null;
try {
@@ -360,12 +360,12 @@ public class PackageParserTest {
final List<ParsedActivity> activities = pkg.getActivities();
for (ParsedActivity activity : activities) {
if ((PACKAGE_NAME + ".MyActivity").equals(activity.getName())) {
- actualDisplayCategory = activity.getTargetDisplayCategory();
+ actualDisplayCategory = activity.getRequiredDisplayCategory();
}
}
} catch (PackageManagerException e) {
assertThat(e.getMessage()).contains(
- "targetDisplayCategory attribute can only consists"
+ "requiredDisplayCategory attribute can only consist"
+ " of alphanumeric characters, '_', and '.'");
} finally {
testFile.delete();
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/PackageManagerLocalSnapshotTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/PackageManagerLocalSnapshotTest.kt
index faa235254665..5f26d6f846aa 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/PackageManagerLocalSnapshotTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/PackageManagerLocalSnapshotTest.kt
@@ -53,6 +53,11 @@ class PackageManagerLocalSnapshotTest {
snapshot.use {
val packageStates = it.packageStates
+ // Check for unmodifiable
+ assertFailsWith(UnsupportedOperationException::class) {
+ it.packageStates.clear()
+ }
+
// Check contents
assertThat(packageStates).containsExactly(
packageStateAll.packageName, packageStateAll,
@@ -78,9 +83,14 @@ class PackageManagerLocalSnapshotTest {
assertThat(filteredOne.getPackageState(packageStateUser10.packageName)).isNull()
filteredThree.use {
- val statesList = mutableListOf<PackageState>()
- assertThat(it.forAllPackageStates { statesList += it })
- assertThat(statesList).containsExactly(packageStateAll, packageStateUser10)
+ // Check for unmodifiable
+ assertFailsWith(UnsupportedOperationException::class) {
+ it.packageStates.clear()
+ }
+ assertThat(it.packageStates).containsExactly(
+ packageStateAll.packageName, packageStateAll,
+ packageStateUser10.packageName, packageStateUser10,
+ )
}
// Call after child close, parent open fails
@@ -96,7 +106,7 @@ class PackageManagerLocalSnapshotTest {
// Call after close fails
assertClosedFailure { snapshot.packageStates }
- assertClosedFailure { filteredOne.forAllPackageStates {} }
+ assertClosedFailure { filteredOne.packageStates }
assertClosedFailure {
filteredTwo.getPackageState(packageStateAll.packageName)
}
@@ -116,9 +126,15 @@ class PackageManagerLocalSnapshotTest {
.isEqualTo(packageStateUser0)
assertThat(it.getPackageState(packageStateUser10.packageName)).isNull()
- val statesList = mutableListOf<PackageState>()
- assertThat(it.forAllPackageStates { statesList += it })
- assertThat(statesList).containsExactly(packageStateAll, packageStateUser0)
+ // Check for unmodifiable
+ assertFailsWith(UnsupportedOperationException::class) {
+ it.packageStates.clear()
+ }
+
+ assertThat(it.packageStates).containsExactly(
+ packageStateAll.packageName, packageStateAll,
+ packageStateUser0.packageName, packageStateUser0,
+ )
}
// Call after close fails
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt
index 4ceae966b7e3..0e2e35f25b15 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt
@@ -54,7 +54,7 @@ class ParsedActivityTest : ParsedMainComponentTest(
ParsedActivity::getTheme,
ParsedActivity::getUiOptions,
ParsedActivity::isSupportsSizeChanges,
- ParsedActivity::getTargetDisplayCategory
+ ParsedActivity::getRequiredDisplayCategory
)
override fun mainComponentSubclassExtraParams() = listOf(
diff --git a/services/tests/mockingservicestests/assets/AppOpsUpgradeTest/appops-version-1.xml b/services/tests/mockingservicestests/assets/AppOpsUpgradeTest/appops-version-1.xml
new file mode 100644
index 000000000000..1dde7dcc4720
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/AppOpsUpgradeTest/appops-version-1.xml
@@ -0,0 +1,781 @@
+<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
+<app-ops v="1">
+ <uid n="1001">
+ <op n="0" m="4" />
+ <op n="1" m="4" />
+ <op n="15" m="0" />
+ <op n="27" m="4" />
+ <op n="87" m="1" />
+ </uid>
+ <uid n="1002">
+ <op n="0" m="4" />
+ <op n="1" m="4" />
+ <op n="15" m="0" />
+ <op n="87" m="1" />
+ </uid>
+ <uid n="10077">
+ <op n="87" m="1" />
+ </uid>
+ <uid n="10079">
+ <op n="116" m="1" />
+ </uid>
+ <uid n="10080">
+ <op n="87" m="1" />
+ </uid>
+ <uid n="10081">
+ <op n="87" m="1" />
+ </uid>
+ <uid n="10086">
+ <op n="87" m="1" />
+ </uid>
+ <uid n="10087">
+ <op n="59" m="1" />
+ <op n="87" m="1" />
+ </uid>
+ <uid n="10090">
+ <op n="0" m="4" />
+ <op n="1" m="4" />
+ </uid>
+ <uid n="10096">
+ <op n="0" m="1" />
+ <op n="1" m="1" />
+ </uid>
+ <uid n="10112">
+ <op n="11" m="1" />
+ <op n="51" m="1" />
+ <op n="59" m="1" />
+ <op n="62" m="1" />
+ <op n="81" m="1" />
+ <op n="83" m="1" />
+ <op n="85" m="1" />
+ <op n="87" m="1" />
+ </uid>
+ <uid n="10113">
+ <op n="0" m="1" />
+ <op n="1" m="1" />
+ <op n="51" m="1" />
+ <op n="62" m="1" />
+ </uid>
+ <uid n="10114">
+ <op n="4" m="1" />
+ </uid>
+ <uid n="10115">
+ <op n="0" m="1" />
+ </uid>
+ <uid n="10116">
+ <op n="0" m="1" />
+ </uid>
+ <uid n="10117">
+ <op n="0" m="4" />
+ <op n="1" m="4" />
+ <op n="13" m="1" />
+ <op n="14" m="1" />
+ <op n="16" m="1" />
+ <op n="26" m="4" />
+ <op n="27" m="4" />
+ </uid>
+ <uid n="10118">
+ <op n="51" m="1" />
+ </uid>
+ <uid n="10119">
+ <op n="11" m="1" />
+ <op n="77" m="1" />
+ <op n="111" m="1" />
+ </uid>
+ <uid n="10120">
+ <op n="0" m="1" />
+ <op n="1" m="1" />
+ <op n="4" m="1" />
+ <op n="6" m="1" />
+ <op n="7" m="1" />
+ <op n="11" m="1" />
+ <op n="13" m="1" />
+ <op n="26" m="1" />
+ <op n="27" m="1" />
+ <op n="54" m="1" />
+ <op n="59" m="1" />
+ <op n="81" m="1" />
+ <op n="83" m="1" />
+ <op n="85" m="1" />
+ <op n="87" m="1" />
+ </uid>
+ <uid n="10121">
+ <op n="59" m="1" />
+ <op n="60" m="1" />
+ <op n="87" m="1" />
+ </uid>
+ <uid n="10122">
+ <op n="0" m="1" />
+ <op n="1" m="1" />
+ <op n="51" m="1" />
+ <op n="59" m="1" />
+ <op n="60" m="1" />
+ <op n="81" m="1" />
+ <op n="83" m="1" />
+ <op n="85" m="1" />
+ <op n="87" m="1" />
+ </uid>
+ <uid n="10123">
+ <op n="0" m="4" />
+ <op n="1" m="4" />
+ <op n="87" m="1" />
+ </uid>
+ <uid n="10124">
+ <op n="11" m="1" />
+ <op n="26" m="1" />
+ </uid>
+ <uid n="10125">
+ <op n="11" m="1" />
+ </uid>
+ <uid n="10127">
+ <op n="0" m="1" />
+ <op n="1" m="1" />
+ <op n="26" m="4" />
+ <op n="27" m="4" />
+ <op n="59" m="1" />
+ <op n="60" m="1" />
+ <op n="65" m="1" />
+ <op n="81" m="1" />
+ <op n="83" m="1" />
+ <op n="85" m="1" />
+ <op n="87" m="1" />
+ <op n="111" m="1" />
+ </uid>
+ <uid n="10129">
+ <op n="0" m="1" />
+ <op n="1" m="1" />
+ </uid>
+ <uid n="10130">
+ <op n="51" m="1" />
+ </uid>
+ <uid n="10131">
+ <op n="0" m="1" />
+ <op n="1" m="1" />
+ <op n="59" m="1" />
+ <op n="60" m="1" />
+ <op n="81" m="1" />
+ <op n="83" m="1" />
+ <op n="85" m="1" />
+ <op n="87" m="1" />
+ </uid>
+ <uid n="10132">
+ <op n="0" m="1" />
+ <op n="1" m="1" />
+ <op n="26" m="1" />
+ <op n="27" m="1" />
+ <op n="69" m="1" />
+ <op n="79" m="1" />
+ </uid>
+ <uid n="10133">
+ <op n="11" m="1" />
+ </uid>
+ <uid n="10136">
+ <op n="0" m="1" />
+ <op n="4" m="1" />
+ <op n="77" m="1" />
+ <op n="87" m="1" />
+ <op n="111" m="1" />
+ <op n="114" m="1" />
+ </uid>
+ <uid n="10137">
+ <op n="62" m="1" />
+ </uid>
+ <uid n="10138">
+ <op n="26" m="4" />
+ </uid>
+ <uid n="10140">
+ <op n="26" m="4" />
+ <op n="27" m="4" />
+ <op n="87" m="1" />
+ </uid>
+ <uid n="10141">
+ <op n="11" m="1" />
+ <op n="27" m="1" />
+ <op n="111" m="1" />
+ </uid>
+ <uid n="10142">
+ <op n="0" m="1" />
+ <op n="1" m="1" />
+ <op n="11" m="1" />
+ </uid>
+ <uid n="10144">
+ <op n="0" m="1" />
+ <op n="1" m="1" />
+ <op n="4" m="1" />
+ <op n="11" m="1" />
+ <op n="27" m="4" />
+ <op n="111" m="1" />
+ </uid>
+ <uid n="10145">
+ <op n="11" m="1" />
+ </uid>
+ <uid n="10149">
+ <op n="11" m="1" />
+ <op n="59" m="1" />
+ <op n="60" m="1" />
+ <op n="81" m="1" />
+ <op n="83" m="1" />
+ <op n="85" m="1" />
+ <op n="87" m="1" />
+ </uid>
+ <uid n="10150">
+ <op n="51" m="1" />
+ </uid>
+ <uid n="10151">
+ <op n="11" m="1" />
+ </uid>
+ <uid n="10152">
+ <op n="11" m="1" />
+ </uid>
+ <uid n="10154">
+ <op n="0" m="4" />
+ <op n="1" m="4" />
+ <op n="27" m="4" />
+ </uid>
+ <uid n="10155">
+ <op n="0" m="1" />
+ <op n="1" m="1" />
+ </uid>
+ <uid n="10157">
+ <op n="13" m="1" />
+ </uid>
+ <uid n="10158">
+ <op n="11" m="1" />
+ </uid>
+ <uid n="10160">
+ <op n="0" m="1" />
+ <op n="1" m="1" />
+ <op n="11" m="1" />
+ <op n="26" m="4" />
+ <op n="27" m="4" />
+ <op n="87" m="1" />
+ </uid>
+ <uid n="10161">
+ <op n="0" m="1" />
+ <op n="1" m="1" />
+ <op n="11" m="1" />
+ <op n="51" m="1" />
+ <op n="77" m="1" />
+ <op n="87" m="1" />
+ <op n="111" m="1" />
+ <op n="114" m="1" />
+ </uid>
+ <uid n="10162">
+ <op n="0" m="1" />
+ <op n="1" m="1" />
+ <op n="15" m="0" />
+ <op n="26" m="4" />
+ <op n="27" m="4" />
+ <op n="87" m="1" />
+ <op n="89" m="0" />
+ </uid>
+ <uid n="10163">
+ <op n="26" m="4" />
+ <op n="27" m="4" />
+ <op n="56" m="4" />
+ <op n="87" m="1" />
+ </uid>
+ <uid n="10164">
+ <op n="26" m="1" />
+ <op n="27" m="4" />
+ <op n="59" m="1" />
+ <op n="60" m="1" />
+ <op n="69" m="1" />
+ <op n="79" m="1" />
+ <op n="81" m="1" />
+ <op n="83" m="1" />
+ <op n="85" m="1" />
+ <op n="87" m="1" />
+ </uid>
+ <uid n="10169">
+ <op n="11" m="1" />
+ </uid>
+ <uid n="10170">
+ <op n="87" m="1" />
+ </uid>
+ <uid n="10171">
+ <op n="26" m="1" />
+ <op n="51" m="1" />
+ <op n="59" m="1" />
+ <op n="60" m="1" />
+ <op n="81" m="1" />
+ <op n="83" m="1" />
+ <op n="85" m="1" />
+ <op n="87" m="1" />
+ </uid>
+ <uid n="10172">
+ <op n="11" m="1" />
+ </uid>
+ <uid n="10173">
+ <op n="0" m="1" />
+ <op n="1" m="1" />
+ <op n="23" m="0" />
+ <op n="26" m="1" />
+ <op n="51" m="1" />
+ <op n="62" m="1" />
+ <op n="65" m="1" />
+ </uid>
+ <uid n="10175">
+ <op n="0" m="1" />
+ <op n="11" m="1" />
+ <op n="26" m="1" />
+ <op n="27" m="1" />
+ </uid>
+ <uid n="10178">
+ <op n="0" m="1" />
+ <op n="1" m="1" />
+ <op n="11" m="1" />
+ <op n="27" m="1" />
+ <op n="87" m="1" />
+ </uid>
+ <uid n="10179">
+ <op n="0" m="1" />
+ <op n="1" m="1" />
+ <op n="4" m="1" />
+ <op n="11" m="1" />
+ <op n="26" m="1" />
+ <op n="27" m="1" />
+ <op n="51" m="1" />
+ <op n="59" m="1" />
+ <op n="60" m="1" />
+ <op n="62" m="1" />
+ <op n="81" m="1" />
+ <op n="83" m="1" />
+ <op n="85" m="1" />
+ <op n="87" m="1" />
+ </uid>
+ <uid n="10180">
+ <op n="11" m="1" />
+ </uid>
+ <uid n="10181">
+ <op n="4" m="1" />
+ <op n="11" m="1" />
+ <op n="26" m="1" />
+ <op n="27" m="1" />
+ <op n="59" m="1" />
+ <op n="62" m="1" />
+ <op n="81" m="1" />
+ <op n="83" m="1" />
+ <op n="85" m="1" />
+ <op n="87" m="1" />
+ </uid>
+ <uid n="1110181">
+ <op n="4" m="1" />
+ <op n="11" m="1" />
+ <op n="26" m="1" />
+ <op n="27" m="1" />
+ <op n="81" m="1" />
+ <op n="83" m="1" />
+ <op n="85" m="1" />
+ <op n="87" m="1" />
+ <op n="107" m="2" />
+ </uid>
+ <uid n="10182">
+ <op n="27" m="4" />
+ </uid>
+ <uid n="10183">
+ <op n="11" m="1" />
+ <op n="26" m="4" />
+ </uid>
+ <uid n="10184">
+ <op n="4" m="1" />
+ <op n="11" m="1" />
+ <op n="13" m="1" />
+ <op n="26" m="1" />
+ <op n="27" m="4" />
+ </uid>
+ <uid n="10185">
+ <op n="8" m="1" />
+ <op n="59" m="1" />
+ <op n="81" m="1" />
+ <op n="83" m="1" />
+ <op n="85" m="1" />
+ <op n="87" m="1" />
+ </uid>
+ <uid n="10187">
+ <op n="11" m="1" />
+ </uid>
+ <uid n="10189">
+ <op n="11" m="1" />
+ <op n="26" m="1" />
+ <op n="27" m="1" />
+ <op n="51" m="1" />
+ </uid>
+ <uid n="10190">
+ <op n="0" m="1" />
+ <op n="13" m="1" />
+ </uid>
+ <uid n="10191">
+ <op n="11" m="1" />
+ </uid>
+ <uid n="10192">
+ <op n="11" m="1" />
+ <op n="13" m="1" />
+ <op n="26" m="1" />
+ <op n="27" m="1" />
+ <op n="51" m="1" />
+ <op n="59" m="1" />
+ <op n="60" m="1" />
+ <op n="81" m="1" />
+ <op n="83" m="1" />
+ <op n="85" m="1" />
+ <op n="87" m="1" />
+ </uid>
+ <uid n="10193">
+ <op n="11" m="1" />
+ </uid>
+ <uid n="10197">
+ <op n="11" m="1" />
+ </uid>
+ <uid n="10198">
+ <op n="0" m="1" />
+ <op n="1" m="1" />
+ <op n="4" m="1" />
+ <op n="11" m="1" />
+ <op n="26" m="1" />
+ <op n="27" m="1" />
+ <op n="59" m="1" />
+ <op n="60" m="1" />
+ <op n="77" m="1" />
+ <op n="81" m="1" />
+ <op n="83" m="1" />
+ <op n="85" m="1" />
+ <op n="87" m="1" />
+ <op n="90" m="1" />
+ <op n="107" m="0" />
+ <op n="111" m="1" />
+ </uid>
+ <uid n="10199">
+ <op n="4" m="1" />
+ <op n="11" m="1" />
+ <op n="26" m="1" />
+ <op n="59" m="1" />
+ <op n="60" m="1" />
+ <op n="62" m="1" />
+ <op n="81" m="1" />
+ <op n="83" m="1" />
+ <op n="85" m="1" />
+ <op n="87" m="1" />
+ </uid>
+ <uid n="10200">
+ <op n="11" m="1" />
+ <op n="65" m="1" />
+ <op n="107" m="1" />
+ </uid>
+ <uid n="1110200">
+ <op n="11" m="1" />
+ <op n="65" m="1" />
+ <op n="107" m="2" />
+ </uid>
+ <uid n="10201">
+ <op n="0" m="1" />
+ <op n="1" m="1" />
+ <op n="4" m="1" />
+ <op n="11" m="1" />
+ <op n="51" m="1" />
+ <op n="62" m="1" />
+ <op n="84" m="0" />
+ <op n="86" m="0" />
+ <op n="87" m="1" />
+ </uid>
+ <uid n="10206">
+ <op n="0" m="4" />
+ <op n="1" m="4" />
+ <op n="26" m="4" />
+ <op n="59" m="1" />
+ <op n="60" m="1" />
+ <op n="87" m="1" />
+ </uid>
+ <uid n="10209">
+ <op n="11" m="1" />
+ <op n="51" m="1" />
+ <op n="59" m="1" />
+ <op n="60" m="1" />
+ <op n="81" m="1" />
+ <op n="83" m="1" />
+ <op n="85" m="1" />
+ <op n="87" m="1" />
+ </uid>
+ <uid n="10210">
+ <op n="51" m="1" />
+ </uid>
+ <uid n="10212">
+ <op n="11" m="1" />
+ <op n="62" m="1" />
+ </uid>
+ <uid n="10214">
+ <op n="26" m="4" />
+ </uid>
+ <uid n="10216">
+ <op n="51" m="1" />
+ <op n="59" m="1" />
+ <op n="60" m="1" />
+ <op n="85" m="1" />
+ <op n="87" m="1" />
+ </uid>
+ <uid n="10225">
+ <op n="0" m="1" />
+ <op n="1" m="1" />
+ <op n="51" m="1" />
+ <op n="59" m="1" />
+ <op n="87" m="1" />
+ </uid>
+ <uid n="10229">
+ <op n="11" m="1" />
+ <op n="59" m="1" />
+ <op n="60" m="1" />
+ <op n="62" m="1" />
+ <op n="87" m="1" />
+ </uid>
+ <uid n="10231">
+ <op n="51" m="1" />
+ </uid>
+ <uid n="10232">
+ <op n="51" m="1" />
+ </uid>
+ <uid n="10234">
+ <op n="0" m="1" />
+ <op n="1" m="1" />
+ <op n="11" m="1" />
+ <op n="13" m="1" />
+ <op n="20" m="1" />
+ <op n="26" m="1" />
+ <op n="27" m="1" />
+ <op n="59" m="1" />
+ <op n="60" m="1" />
+ <op n="81" m="1" />
+ <op n="83" m="1" />
+ <op n="85" m="1" />
+ <op n="87" m="1" />
+ </uid>
+ <uid n="10235">
+ <op n="59" m="1" />
+ <op n="60" m="1" />
+ <op n="87" m="1" />
+ </uid>
+ <uid n="10237">
+ <op n="0" m="4" />
+ <op n="1" m="4" />
+ </uid>
+ <uid n="10238">
+ <op n="26" m="4" />
+ <op n="27" m="4" />
+ <op n="87" m="1" />
+ </uid>
+ <uid n="10240">
+ <op n="112" m="1" />
+ </uid>
+ <uid n="10241">
+ <op n="59" m="1" />
+ <op n="87" m="1" />
+ </uid>
+ <uid n="10245">
+ <op n="13" m="1" />
+ <op n="51" m="1" />
+ </uid>
+ <uid n="10247">
+ <op n="0" m="1" />
+ <op n="59" m="1" />
+ <op n="60" m="1" />
+ <op n="81" m="1" />
+ <op n="83" m="1" />
+ <op n="85" m="1" />
+ <op n="87" m="0" />
+ <op n="90" m="1" />
+ </uid>
+ <uid n="10254">
+ <op n="11" m="1" />
+ </uid>
+ <uid n="10255">
+ <op n="11" m="1" />
+ </uid>
+ <uid n="10256">
+ <op n="87" m="1" />
+ </uid>
+ <uid n="10258">
+ <op n="11" m="1" />
+ </uid>
+ <uid n="10260">
+ <op n="51" m="1" />
+ <op n="59" m="1" />
+ <op n="60" m="1" />
+ <op n="87" m="1" />
+ </uid>
+ <uid n="10262">
+ <op n="15" m="0" />
+ </uid>
+ <uid n="10266">
+ <op n="0" m="4" />
+ </uid>
+ <uid n="10267">
+ <op n="0" m="1" />
+ <op n="1" m="1" />
+ <op n="4" m="1" />
+ <op n="59" m="1" />
+ <op n="60" m="1" />
+ <op n="62" m="1" />
+ <op n="77" m="1" />
+ <op n="81" m="1" />
+ <op n="83" m="1" />
+ <op n="85" m="1" />
+ <op n="87" m="1" />
+ <op n="107" m="2" />
+ <op n="111" m="1" />
+ </uid>
+ <uid n="10268">
+ <op n="4" m="1" />
+ <op n="11" m="1" />
+ <op n="62" m="1" />
+ </uid>
+ <uid n="10269">
+ <op n="11" m="1" />
+ <op n="26" m="1" />
+ <op n="27" m="1" />
+ <op n="59" m="1" />
+ <op n="60" m="1" />
+ <op n="85" m="1" />
+ <op n="87" m="1" />
+ </uid>
+ <pkg n="com.google.android.iwlan">
+ <uid n="0">
+ <op n="1" />
+ <op n="75" m="0" />
+ </uid>
+ </pkg>
+ <pkg n="com.android.phone">
+ <uid n="0">
+ <op n="1" />
+ <op n="75" m="0" />
+ </uid>
+ </pkg>
+ <pkg n="android">
+ <uid n="1000">
+ <op n="0">
+ <st n="214748364801" t="1670287941040" />
+ </op>
+ <op n="4">
+ <st n="214748364801" t="1670289665522" />
+ </op>
+ <op n="6">
+ <st n="214748364801" t="1670287946650" />
+ </op>
+ <op n="8">
+ <st n="214748364801" t="1670289624396" />
+ </op>
+ <op n="14">
+ <st n="214748364801" t="1670287951031" />
+ </op>
+ <op n="40">
+ <st n="214748364801" t="1670291786337" d="156" />
+ </op>
+ <op n="41">
+ <st id="SensorNotificationService" n="214748364801" t="1670287585567" d="4251183" />
+ <st id="CountryDetector" n="214748364801" t="1670287583306" d="6700" />
+ </op>
+ <op n="43">
+ <st n="214748364801" r="1670291755062" />
+ </op>
+ <op n="61">
+ <st n="214748364801" r="1670291754997" />
+ </op>
+ <op n="105">
+ <st n="214748364801" r="1670291473903" />
+ <st id="GnssService" n="214748364801" r="1670288044920" />
+ </op>
+ <op n="111">
+ <st n="214748364801" t="1670291441554" />
+ </op>
+ </uid>
+ </pkg>
+ <pkg n="com.android.server.telecom">
+ <uid n="1000">
+ <op n="6">
+ <st n="214748364801" t="1670287609092" />
+ </op>
+ <op n="111">
+ <st n="214748364801" t="1670287583728" />
+ </op>
+ </uid>
+ </pkg>
+ <pkg n="com.android.settings">
+ <uid n="1000">
+ <op n="43">
+ <st n="214748364801" r="1670291447349" />
+ </op>
+ <op n="105">
+ <st n="214748364801" r="1670291399231" />
+ </op>
+ <op n="111">
+ <st n="214748364801" t="1670291756910" />
+ </op>
+ </uid>
+ </pkg>
+ <pkg n="com.android.phone">
+ <uid n="1001">
+ <op n="15">
+ <st n="214748364801" t="1670287951022" />
+ </op>
+ <op n="40">
+ <st n="214748364801" t="1670291786177" />
+ </op>
+ <op n="105">
+ <st n="214748364801" r="1670291756403" />
+ </op>
+ </uid>
+ </pkg>
+ <pkg n="com.android.bluetooth">
+ <uid n="1002">
+ <op n="4">
+ <st n="214748364801" t="1670289671076" />
+ </op>
+ <op n="40">
+ <st n="214748364801" t="1670287585676" d="8" />
+ </op>
+ <op n="43">
+ <st n="214748364801" r="1670287585818" />
+ </op>
+ <op n="77">
+ <st n="214748364801" t="1670288037629" />
+ </op>
+ <op n="111">
+ <st n="214748364801" t="1670287592081" />
+ </op>
+ </uid>
+ </pkg>
+ <pkg n="com.android.vending">
+ <uid n="10136">
+ <op n="40">
+ <st n="429496729601" t="1670289621210" d="114" />
+ <st n="858993459201" t="1670289879730" d="349" />
+ <st n="1288490188801" t="1670287942622" d="937" />
+ </op>
+ <op n="43">
+ <st n="429496729601" r="1670289755305" />
+ <st n="858993459201" r="1670288019246" />
+ <st n="1073741824001" r="1670289571783" />
+ <st n="1288490188801" r="1670289373336" />
+ </op>
+ <op n="76">
+ <st n="429496729601" t="1670289748735" d="15991" />
+ <st n="858993459201" t="1670291395180" d="79201" />
+ <st n="1073741824001" t="1670291395168" d="12" />
+ <st n="1288490188801" t="1670291526029" d="3" />
+ <st n="1503238553601" t="1670291526032" d="310718" />
+ </op>
+ <op n="105">
+ <st n="429496729601" r="1670289538910" />
+ <st n="858993459201" r="1670288054519" />
+ <st n="1073741824001" r="1670287599379" />
+ <st n="1288490188801" r="1670289526854" />
+ <st n="1503238553601" r="1670289528242" />
+ </op>
+ </uid>
+ </pkg>
+ <pkg n="com.android.nfc">
+ <uid n="1027">
+ <op n="40">
+ <st n="214748364801" t="1670291786330" d="22" />
+ </op>
+ </uid>
+ </pkg>
+</app-ops> \ No newline at end of file
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
index c87fd26fbe82..8d78cd6fb012 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -195,6 +195,12 @@ public class BroadcastQueueModernImplTest {
false, null, false, null);
}
+ private void enqueueOrReplaceBroadcast(BroadcastProcessQueue queue,
+ BroadcastRecord record, int recordIndex, long enqueueTime) {
+ queue.enqueueOrReplaceBroadcast(record, recordIndex);
+ record.enqueueTime = enqueueTime;
+ }
+
@Test
public void testRunnableList_Simple() {
assertRunnableList(List.of(), mHead);
@@ -549,29 +555,32 @@ public class BroadcastQueueModernImplTest {
mConstants.MAX_CONSECUTIVE_URGENT_DISPATCHES = 2;
BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants,
PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN));
+ long timeCounter = 100;
// mix of broadcasts, with more than 2 fg/urgent
- queue.enqueueOrReplaceBroadcast(
- makeBroadcastRecord(new Intent(Intent.ACTION_TIMEZONE_CHANGED)), 0);
- queue.enqueueOrReplaceBroadcast(
- makeBroadcastRecord(new Intent(Intent.ACTION_ALARM_CHANGED)), 0);
- queue.enqueueOrReplaceBroadcast(
- makeBroadcastRecord(new Intent(Intent.ACTION_TIME_TICK)), 0);
- queue.enqueueOrReplaceBroadcast(
+ enqueueOrReplaceBroadcast(queue,
+ makeBroadcastRecord(new Intent(Intent.ACTION_TIMEZONE_CHANGED)),
+ 0, timeCounter++);
+ enqueueOrReplaceBroadcast(queue,
+ makeBroadcastRecord(new Intent(Intent.ACTION_ALARM_CHANGED)),
+ 0, timeCounter++);
+ enqueueOrReplaceBroadcast(queue,
+ makeBroadcastRecord(new Intent(Intent.ACTION_TIME_TICK)), 0, timeCounter++);
+ enqueueOrReplaceBroadcast(queue,
makeBroadcastRecord(new Intent(Intent.ACTION_LOCALE_CHANGED)
- .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0);
- queue.enqueueOrReplaceBroadcast(
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0, timeCounter++);
+ enqueueOrReplaceBroadcast(queue,
makeBroadcastRecord(new Intent(Intent.ACTION_APPLICATION_PREFERENCES),
- optInteractive), 0);
- queue.enqueueOrReplaceBroadcast(
+ optInteractive), 0, timeCounter++);
+ enqueueOrReplaceBroadcast(queue,
makeBroadcastRecord(new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE),
- optInteractive), 0);
- queue.enqueueOrReplaceBroadcast(
+ optInteractive), 0, timeCounter++);
+ enqueueOrReplaceBroadcast(queue,
makeBroadcastRecord(new Intent(Intent.ACTION_INPUT_METHOD_CHANGED)
- .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0);
- queue.enqueueOrReplaceBroadcast(
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0, timeCounter++);
+ enqueueOrReplaceBroadcast(queue,
makeBroadcastRecord(new Intent(Intent.ACTION_NEW_OUTGOING_CALL),
- optInteractive), 0);
+ optInteractive), 0, timeCounter++);
queue.makeActiveNextPending();
assertEquals(Intent.ACTION_LOCALE_CHANGED, queue.getActive().intent.getAction());
@@ -592,6 +601,133 @@ public class BroadcastQueueModernImplTest {
}
/**
+ * Verify that offload broadcasts are not starved because of broadcasts in higher priority
+ * queues.
+ */
+ @Test
+ public void testOffloadStarvation() {
+ final BroadcastOptions optInteractive = BroadcastOptions.makeBasic();
+ optInteractive.setInteractive(true);
+
+ mConstants.MAX_CONSECUTIVE_URGENT_DISPATCHES = 1;
+ mConstants.MAX_CONSECUTIVE_NORMAL_DISPATCHES = 2;
+ final BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants,
+ PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN));
+ long timeCounter = 100;
+
+ // mix of broadcasts, with more than 2 normal
+ enqueueOrReplaceBroadcast(queue,
+ makeBroadcastRecord(new Intent(Intent.ACTION_BOOT_COMPLETED)
+ .addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 0, timeCounter++);
+ enqueueOrReplaceBroadcast(queue,
+ makeBroadcastRecord(new Intent(Intent.ACTION_TIMEZONE_CHANGED)),
+ 0, timeCounter++);
+ enqueueOrReplaceBroadcast(queue,
+ makeBroadcastRecord(new Intent(Intent.ACTION_PACKAGE_CHANGED)
+ .addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 0, timeCounter++);
+ enqueueOrReplaceBroadcast(queue,
+ makeBroadcastRecord(new Intent(Intent.ACTION_ALARM_CHANGED)),
+ 0, timeCounter++);
+ enqueueOrReplaceBroadcast(queue,
+ makeBroadcastRecord(new Intent(Intent.ACTION_TIME_TICK)), 0, timeCounter++);
+ enqueueOrReplaceBroadcast(queue,
+ makeBroadcastRecord(new Intent(Intent.ACTION_LOCALE_CHANGED)
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0, timeCounter++);
+ enqueueOrReplaceBroadcast(queue,
+ makeBroadcastRecord(new Intent(Intent.ACTION_APPLICATION_PREFERENCES),
+ optInteractive), 0, timeCounter++);
+ enqueueOrReplaceBroadcast(queue,
+ makeBroadcastRecord(new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE),
+ optInteractive), 0, timeCounter++);
+ enqueueOrReplaceBroadcast(queue,
+ makeBroadcastRecord(new Intent(Intent.ACTION_INPUT_METHOD_CHANGED)
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0, timeCounter++);
+ enqueueOrReplaceBroadcast(queue,
+ makeBroadcastRecord(new Intent(Intent.ACTION_NEW_OUTGOING_CALL),
+ optInteractive), 0, timeCounter++);
+
+ queue.makeActiveNextPending();
+ assertEquals(Intent.ACTION_LOCALE_CHANGED, queue.getActive().intent.getAction());
+ // after MAX_CONSECUTIVE_URGENT_DISPATCHES expect an ordinary one next
+ queue.makeActiveNextPending();
+ assertEquals(Intent.ACTION_TIMEZONE_CHANGED, queue.getActive().intent.getAction());
+ // and then back to prioritizing urgent ones
+ queue.makeActiveNextPending();
+ assertEquals(Intent.ACTION_APPLICATION_PREFERENCES, queue.getActive().intent.getAction());
+ // after MAX_CONSECUTIVE_URGENT_DISPATCHES, again an ordinary one next
+ queue.makeActiveNextPending();
+ assertEquals(Intent.ACTION_ALARM_CHANGED, queue.getActive().intent.getAction());
+ // and then back to prioritizing urgent ones
+ queue.makeActiveNextPending();
+ assertEquals(AppWidgetManager.ACTION_APPWIDGET_UPDATE,
+ queue.getActive().intent.getAction());
+ // after MAX_CONSECUTIVE_URGENT_DISPATCHES and MAX_CONSECUTIVE_NORMAL_DISPATCHES,
+ // expect an offload one
+ queue.makeActiveNextPending();
+ assertEquals(Intent.ACTION_BOOT_COMPLETED, queue.getActive().intent.getAction());
+ // and then back to prioritizing urgent ones
+ queue.makeActiveNextPending();
+ assertEquals(Intent.ACTION_INPUT_METHOD_CHANGED, queue.getActive().intent.getAction());
+ }
+
+ /**
+ * Verify that BroadcastProcessQueue#setPrioritizeEarliest() works as expected.
+ */
+ @Test
+ public void testPrioritizeEarliest() {
+ final BroadcastOptions optInteractive = BroadcastOptions.makeBasic();
+ optInteractive.setInteractive(true);
+
+ BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants,
+ PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN));
+ queue.setPrioritizeEarliest(true);
+ long timeCounter = 100;
+
+ enqueueOrReplaceBroadcast(queue,
+ makeBroadcastRecord(new Intent(Intent.ACTION_BOOT_COMPLETED)
+ .addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 0, timeCounter++);
+ enqueueOrReplaceBroadcast(queue,
+ makeBroadcastRecord(new Intent(Intent.ACTION_TIMEZONE_CHANGED)),
+ 0, timeCounter++);
+ enqueueOrReplaceBroadcast(queue,
+ makeBroadcastRecord(new Intent(Intent.ACTION_PACKAGE_CHANGED)
+ .addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 0, timeCounter++);
+ enqueueOrReplaceBroadcast(queue,
+ makeBroadcastRecord(new Intent(Intent.ACTION_ALARM_CHANGED)),
+ 0, timeCounter++);
+ enqueueOrReplaceBroadcast(queue,
+ makeBroadcastRecord(new Intent(Intent.ACTION_TIME_TICK)),
+ 0, timeCounter++);
+ enqueueOrReplaceBroadcast(queue,
+ makeBroadcastRecord(new Intent(Intent.ACTION_LOCALE_CHANGED)
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0, timeCounter++);
+ enqueueOrReplaceBroadcast(queue,
+ makeBroadcastRecord(new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE),
+ optInteractive), 0, timeCounter++);
+
+ // When we mark BroadcastProcessQueue to prioritize earliest, we should
+ // expect to dispatch broadcasts in the order they were enqueued
+ queue.makeActiveNextPending();
+ assertEquals(Intent.ACTION_BOOT_COMPLETED, queue.getActive().intent.getAction());
+ queue.makeActiveNextPending();
+ assertEquals(Intent.ACTION_TIMEZONE_CHANGED, queue.getActive().intent.getAction());
+ // after MAX_CONSECUTIVE_URGENT_DISPATCHES expect an ordinary one next
+ queue.makeActiveNextPending();
+ assertEquals(Intent.ACTION_PACKAGE_CHANGED, queue.getActive().intent.getAction());
+ // and then back to prioritizing urgent ones
+ queue.makeActiveNextPending();
+ assertEquals(Intent.ACTION_ALARM_CHANGED, queue.getActive().intent.getAction());
+ queue.makeActiveNextPending();
+ assertEquals(Intent.ACTION_TIME_TICK, queue.getActive().intent.getAction());
+ queue.makeActiveNextPending();
+ assertEquals(Intent.ACTION_LOCALE_CHANGED, queue.getActive().intent.getAction());
+ // verify the reset-count-then-resume worked too
+ queue.makeActiveNextPending();
+ assertEquals(AppWidgetManager.ACTION_APPWIDGET_UPDATE,
+ queue.getActive().intent.getAction());
+ }
+
+ /**
* Verify that sending a broadcast that removes any matching pending
* broadcasts is applied as expected.
*/
diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
index a8d894511213..d3fa92ce68d7 100644
--- a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
@@ -647,15 +647,15 @@ public class GameManagerServiceTests {
}
}
- private void checkReportedOptedInGameModes(GameManagerService gameManagerService,
- int... requiredOptedInModes) {
- Arrays.sort(requiredOptedInModes);
- // check GetModeInfo.getOptedInGameModes
+ private void checkReportedOverriddenGameModes(GameManagerService gameManagerService,
+ int... requiredOverriddenModes) {
+ Arrays.sort(requiredOverriddenModes);
+ // check GetModeInfo.getOverriddenGameModes
GameModeInfo info = gameManagerService.getGameModeInfo(mPackageName, USER_ID_1);
assertNotNull(info);
- int[] optedInModes = info.getOptedInGameModes();
- Arrays.sort(optedInModes);
- assertArrayEquals(requiredOptedInModes, optedInModes);
+ int[] overriddenModes = info.getOverriddenGameModes();
+ Arrays.sort(overriddenModes);
+ assertArrayEquals(requiredOverriddenModes, overriddenModes);
}
private void checkDownscaling(GameManagerService gameManagerService,
@@ -697,7 +697,7 @@ public class GameManagerServiceTests {
assertEquals(fps, config.getGameModeConfiguration(gameMode).getFps());
}
- private boolean checkOptedIn(GameManagerService gameManagerService, int gameMode) {
+ private boolean checkOverridden(GameManagerService gameManagerService, int gameMode) {
GameManagerService.GamePackageConfiguration config =
gameManagerService.getConfig(mPackageName, USER_ID_1);
return config.willGamePerformOptimizations(gameMode);
@@ -870,8 +870,8 @@ public class GameManagerServiceTests {
mTestLooper.getLooper());
startUser(gameManagerService, USER_ID_1);
- assertFalse(checkOptedIn(gameManagerService, GameManager.GAME_MODE_PERFORMANCE));
- assertFalse(checkOptedIn(gameManagerService, GameManager.GAME_MODE_BATTERY));
+ assertFalse(checkOverridden(gameManagerService, GameManager.GAME_MODE_PERFORMANCE));
+ assertFalse(checkOverridden(gameManagerService, GameManager.GAME_MODE_BATTERY));
checkFps(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 0);
gameManagerService.setGameModeConfigOverride(mPackageName, USER_ID_1, 3, "40",
@@ -884,9 +884,9 @@ public class GameManagerServiceTests {
mockInterventionsDisabledAllOptInFromXml();
gameManagerService.updateConfigsForUser(USER_ID_1, false, mPackageName);
- assertTrue(checkOptedIn(gameManagerService, GameManager.GAME_MODE_PERFORMANCE));
+ assertTrue(checkOverridden(gameManagerService, GameManager.GAME_MODE_PERFORMANCE));
// opt-in is still false for battery mode as override exists
- assertFalse(checkOptedIn(gameManagerService, GameManager.GAME_MODE_BATTERY));
+ assertFalse(checkOverridden(gameManagerService, GameManager.GAME_MODE_BATTERY));
}
/**
@@ -1310,7 +1310,7 @@ public class GameManagerServiceTests {
checkReportedAvailableGameModes(gameManagerService, GameManager.GAME_MODE_PERFORMANCE,
GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_STANDARD,
GameManager.GAME_MODE_CUSTOM);
- checkReportedOptedInGameModes(gameManagerService);
+ checkReportedOverriddenGameModes(gameManagerService);
assertEquals(new GameModeConfiguration.Builder()
.setFpsOverride(30)
@@ -1337,7 +1337,7 @@ public class GameManagerServiceTests {
checkReportedAvailableGameModes(gameManagerService,
GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_STANDARD,
GameManager.GAME_MODE_CUSTOM);
- checkReportedOptedInGameModes(gameManagerService);
+ checkReportedOverriddenGameModes(gameManagerService);
assertNotNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_BATTERY));
assertNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE));
@@ -1357,7 +1357,7 @@ public class GameManagerServiceTests {
checkReportedAvailableGameModes(gameManagerService,
GameManager.GAME_MODE_PERFORMANCE, GameManager.GAME_MODE_STANDARD,
GameManager.GAME_MODE_CUSTOM);
- checkReportedOptedInGameModes(gameManagerService);
+ checkReportedOverriddenGameModes(gameManagerService);
assertNotNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE));
assertNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_BATTERY));
@@ -1382,7 +1382,7 @@ public class GameManagerServiceTests {
}
@Test
- public void testGetGameModeInfoWithAllGameModesOptedIn_noDeviceConfig()
+ public void testGetGameModeInfoWithAllGameModesOverridden_noDeviceConfig()
throws Exception {
mockModifyGameModeGranted();
mockInterventionsEnabledAllOptInFromXml();
@@ -1390,14 +1390,14 @@ public class GameManagerServiceTests {
GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
GameModeInfo gameModeInfo = gameManagerService.getGameModeInfo(mPackageName, USER_ID_1);
assertEquals(GameManager.GAME_MODE_STANDARD, gameModeInfo.getActiveGameMode());
- verifyAllModesOptedInAndInterventionsAvailable(gameManagerService, gameModeInfo);
+ verifyAllModesOverriddenAndInterventionsAvailable(gameManagerService, gameModeInfo);
assertNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_BATTERY));
assertNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE));
}
@Test
- public void testGetGameModeInfoWithAllGameModesOptedIn_allDeviceConfig()
+ public void testGetGameModeInfoWithAllGameModesOverridden_allDeviceConfig()
throws Exception {
mockModifyGameModeGranted();
mockInterventionsEnabledAllOptInFromXml();
@@ -1405,26 +1405,26 @@ public class GameManagerServiceTests {
GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
GameModeInfo gameModeInfo = gameManagerService.getGameModeInfo(mPackageName, USER_ID_1);
assertEquals(GameManager.GAME_MODE_STANDARD, gameModeInfo.getActiveGameMode());
- verifyAllModesOptedInAndInterventionsAvailable(gameManagerService, gameModeInfo);
+ verifyAllModesOverriddenAndInterventionsAvailable(gameManagerService, gameModeInfo);
assertNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_BATTERY));
assertNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE));
}
- private void verifyAllModesOptedInAndInterventionsAvailable(
+ private void verifyAllModesOverriddenAndInterventionsAvailable(
GameManagerService gameManagerService,
GameModeInfo gameModeInfo) {
checkReportedAvailableGameModes(gameManagerService,
GameManager.GAME_MODE_PERFORMANCE, GameManager.GAME_MODE_BATTERY,
GameManager.GAME_MODE_STANDARD, GameManager.GAME_MODE_CUSTOM);
- checkReportedOptedInGameModes(gameManagerService,
+ checkReportedOverriddenGameModes(gameManagerService,
GameManager.GAME_MODE_PERFORMANCE, GameManager.GAME_MODE_BATTERY);
assertTrue(gameModeInfo.isFpsOverrideAllowed());
assertTrue(gameModeInfo.isDownscalingAllowed());
}
@Test
- public void testGetGameModeInfoWithBatteryModeOptedIn_withBatteryDeviceConfig()
+ public void testGetGameModeInfoWithBatteryModeOverridden_withBatteryDeviceConfig()
throws Exception {
mockModifyGameModeGranted();
mockInterventionsEnabledBatteryOptInFromXml();
@@ -1435,14 +1435,14 @@ public class GameManagerServiceTests {
checkReportedAvailableGameModes(gameManagerService, GameManager.GAME_MODE_BATTERY,
GameManager.GAME_MODE_STANDARD, GameManager.GAME_MODE_CUSTOM);
- checkReportedOptedInGameModes(gameManagerService, GameManager.GAME_MODE_BATTERY);
+ checkReportedOverriddenGameModes(gameManagerService, GameManager.GAME_MODE_BATTERY);
assertNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_BATTERY));
assertNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE));
}
@Test
- public void testGetGameModeInfoWithPerformanceModeOptedIn_withAllDeviceConfig()
+ public void testGetGameModeInfoWithPerformanceModeOverridden_withAllDeviceConfig()
throws Exception {
mockModifyGameModeGranted();
mockInterventionsEnabledPerformanceOptInFromXml();
@@ -1454,7 +1454,7 @@ public class GameManagerServiceTests {
checkReportedAvailableGameModes(gameManagerService, GameManager.GAME_MODE_PERFORMANCE,
GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_STANDARD,
GameManager.GAME_MODE_CUSTOM);
- checkReportedOptedInGameModes(gameManagerService, GameManager.GAME_MODE_PERFORMANCE);
+ checkReportedOverriddenGameModes(gameManagerService, GameManager.GAME_MODE_PERFORMANCE);
assertNotNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_BATTERY));
assertNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE));
@@ -1993,7 +1993,7 @@ public class GameManagerServiceTests {
}
@Test
- public void testResetInterventions_onGameModeOptedIn() throws Exception {
+ public void testResetInterventions_onGameModeOverridden() throws Exception {
mockModifyGameModeGranted();
String configStringBefore =
"mode=2,downscaleFactor=1.0,fps=90";
diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java
index 298dbf47d86d..302fa0f0c528 100644
--- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java
@@ -16,23 +16,32 @@
package com.android.server.appop;
+import static android.app.AppOpsManager.OP_SCHEDULE_EXACT_ALARM;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.when;
import android.app.AppOpsManager;
import android.content.Context;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
import android.content.res.AssetManager;
import android.os.Handler;
-import android.os.HandlerThread;
+import android.os.UserHandle;
import android.util.Log;
import android.util.SparseArray;
import android.util.SparseIntArray;
@@ -42,11 +51,20 @@ import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.util.ArrayUtils;
import com.android.modules.utils.TypedXmlPullParser;
+import com.android.server.LocalServices;
+import com.android.server.SystemServerInitThreadPool;
+import com.android.server.pm.UserManagerInternal;
+import com.android.server.pm.permission.PermissionManagerServiceInternal;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
import org.xmlpull.v1.XmlPullParser;
import java.io.File;
@@ -64,29 +82,39 @@ public class AppOpsUpgradeTest {
private static final String TAG = AppOpsUpgradeTest.class.getSimpleName();
private static final String APP_OPS_UNVERSIONED_ASSET_PATH =
"AppOpsUpgradeTest/appops-unversioned.xml";
+ private static final String APP_OPS_VERSION_1_ASSET_PATH =
+ "AppOpsUpgradeTest/appops-version-1.xml";
private static final String APP_OPS_FILENAME = "appops-test.xml";
private static final int NON_DEFAULT_OPS_IN_FILE = 4;
- private static final int CURRENT_VERSION = 1;
- private File mAppOpsFile;
- private Context mContext;
+ private static final Context sContext = InstrumentationRegistry.getTargetContext();
+ private static final File sAppOpsFile = new File(sContext.getFilesDir(), APP_OPS_FILENAME);
+
+ private Context mTestContext;
+ private MockitoSession mMockitoSession;
+
+ @Mock
+ private PackageManagerInternal mPackageManagerInternal;
+ @Mock
+ private PackageManager mPackageManager;
+ @Mock
+ private UserManagerInternal mUserManagerInternal;
+ @Mock
+ private PermissionManagerServiceInternal mPermissionManagerInternal;
+ @Mock
private Handler mHandler;
- private void extractAppOpsFile() {
- mAppOpsFile.getParentFile().mkdirs();
- if (mAppOpsFile.exists()) {
- mAppOpsFile.delete();
- }
- try (FileOutputStream out = new FileOutputStream(mAppOpsFile);
- InputStream in = mContext.getAssets().open(APP_OPS_UNVERSIONED_ASSET_PATH,
- AssetManager.ACCESS_BUFFER)) {
+ private static void extractAppOpsFile(String assetPath) {
+ sAppOpsFile.getParentFile().mkdirs();
+ try (FileOutputStream out = new FileOutputStream(sAppOpsFile);
+ InputStream in = sContext.getAssets().open(assetPath, AssetManager.ACCESS_BUFFER)) {
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = in.read(buffer)) >= 0) {
out.write(buffer, 0, bytesRead);
}
out.flush();
- Log.d(TAG, "Successfully copied xml to " + mAppOpsFile.getAbsolutePath());
+ Log.d(TAG, "Successfully copied xml to " + sAppOpsFile.getAbsolutePath());
} catch (IOException exc) {
Log.e(TAG, "Exception while copying appops xml", exc);
fail();
@@ -98,7 +126,7 @@ public class AppOpsUpgradeTest {
int numberOfNonDefaultOps = 0;
final int defaultModeOp1 = AppOpsManager.opToDefaultMode(op1);
final int defaultModeOp2 = AppOpsManager.opToDefaultMode(op2);
- for(int i = 0; i < uidStates.size(); i++) {
+ for (int i = 0; i < uidStates.size(); i++) {
final AppOpsServiceImpl.UidState uidState = uidStates.valueAt(i);
SparseIntArray opModes = uidState.getNonDefaultUidModes();
if (opModes != null) {
@@ -132,41 +160,191 @@ public class AppOpsUpgradeTest {
@Before
public void setUp() {
- mContext = InstrumentationRegistry.getTargetContext();
- mAppOpsFile = new File(mContext.getFilesDir(), APP_OPS_FILENAME);
- extractAppOpsFile();
- HandlerThread handlerThread = new HandlerThread(TAG);
- handlerThread.start();
- mHandler = new Handler(handlerThread.getLooper());
+ if (sAppOpsFile.exists()) {
+ sAppOpsFile.delete();
+ }
+
+ mMockitoSession = mockitoSession()
+ .initMocks(this)
+ .spyStatic(LocalServices.class)
+ .mockStatic(SystemServerInitThreadPool.class)
+ .strictness(Strictness.LENIENT)
+ .startMocking();
+
+ doReturn(mPermissionManagerInternal).when(
+ () -> LocalServices.getService(PermissionManagerServiceInternal.class));
+ doReturn(mUserManagerInternal).when(
+ () -> LocalServices.getService(UserManagerInternal.class));
+ doReturn(mPackageManagerInternal).when(
+ () -> LocalServices.getService(PackageManagerInternal.class));
+
+ mTestContext = spy(sContext);
+
+ // Pretend everybody has all permissions
+ doNothing().when(mTestContext).enforcePermission(anyString(), anyInt(), anyInt(),
+ nullable(String.class));
+
+ doReturn(mPackageManager).when(mTestContext).getPackageManager();
+
+ // Stub out package calls to disable AppOpsService#updatePermissionRevokedCompat
+ doReturn(null).when(mPackageManager).getPackagesForUid(anyInt());
+ }
+
+ @After
+ public void tearDown() {
+ mMockitoSession.finishMocking();
}
@Test
- public void testUpgradeFromNoVersion() throws Exception {
- AppOpsDataParser parser = new AppOpsDataParser(mAppOpsFile);
+ public void upgradeRunAnyInBackground() {
+ extractAppOpsFile(APP_OPS_UNVERSIONED_ASSET_PATH);
+
+ AppOpsServiceImpl testService = new AppOpsServiceImpl(sAppOpsFile, mHandler, mTestContext);
+
+ testService.upgradeRunAnyInBackgroundLocked();
+ assertSameModes(testService.mUidStates, AppOpsManager.OP_RUN_IN_BACKGROUND,
+ AppOpsManager.OP_RUN_ANY_IN_BACKGROUND);
+ }
+
+ private static int getModeInFile(int uid) {
+ switch (uid) {
+ case 10198:
+ return 0;
+ case 10200:
+ return 1;
+ case 1110200:
+ case 10267:
+ case 1110181:
+ return 2;
+ default:
+ return AppOpsManager.opToDefaultMode(OP_SCHEDULE_EXACT_ALARM);
+ }
+ }
+
+ @Test
+ public void upgradeScheduleExactAlarm() {
+ extractAppOpsFile(APP_OPS_VERSION_1_ASSET_PATH);
+
+ String[] packageNames = {"p1", "package2", "pkg3", "package.4", "pkg-5", "pkg.6"};
+ int[] appIds = {10267, 10181, 10198, 10199, 10200, 4213};
+ int[] userIds = {0, 10, 11};
+
+ doReturn(userIds).when(mUserManagerInternal).getUserIds();
+
+ doReturn(packageNames).when(mPermissionManagerInternal).getAppOpPermissionPackages(
+ AppOpsManager.opToPermission(OP_SCHEDULE_EXACT_ALARM));
+
+ doAnswer(invocation -> {
+ String pkg = invocation.getArgument(0);
+ int index = ArrayUtils.indexOf(packageNames, pkg);
+ if (index < 0) {
+ return index;
+ }
+ int userId = invocation.getArgument(2);
+ return UserHandle.getUid(userId, appIds[index]);
+ }).when(mPackageManagerInternal).getPackageUid(anyString(), anyLong(), anyInt());
+
+ AppOpsServiceImpl testService = new AppOpsServiceImpl(sAppOpsFile, mHandler, mTestContext);
+
+ testService.upgradeScheduleExactAlarmLocked();
+
+ for (int userId : userIds) {
+ for (int appId : appIds) {
+ final int uid = UserHandle.getUid(userId, appId);
+ final int previousMode = getModeInFile(uid);
+
+ final int expectedMode;
+ if (previousMode == AppOpsManager.opToDefaultMode(OP_SCHEDULE_EXACT_ALARM)) {
+ expectedMode = AppOpsManager.MODE_ALLOWED;
+ } else {
+ expectedMode = previousMode;
+ }
+ final AppOpsServiceImpl.UidState uidState = testService.mUidStates.get(uid);
+ assertEquals(expectedMode, uidState.getUidMode(OP_SCHEDULE_EXACT_ALARM));
+ }
+ }
+
+ // These uids don't even declare the permission. So should stay as default / empty.
+ int[] unrelatedUidsInFile = {10225, 10178};
+
+ for (int uid : unrelatedUidsInFile) {
+ final AppOpsServiceImpl.UidState uidState = testService.mUidStates.get(uid);
+ assertEquals(AppOpsManager.opToDefaultMode(OP_SCHEDULE_EXACT_ALARM),
+ uidState.getUidMode(OP_SCHEDULE_EXACT_ALARM));
+ }
+ }
+
+ @Test
+ public void upgradeFromNoFile() {
+ assertFalse(sAppOpsFile.exists());
+
+ AppOpsServiceImpl testService = spy(
+ new AppOpsServiceImpl(sAppOpsFile, mHandler, mTestContext));
+
+ doNothing().when(testService).upgradeRunAnyInBackgroundLocked();
+ doNothing().when(testService).upgradeScheduleExactAlarmLocked();
+
+ // trigger upgrade
+ testService.systemReady();
+
+ verify(testService, never()).upgradeRunAnyInBackgroundLocked();
+ verify(testService, never()).upgradeScheduleExactAlarmLocked();
+
+ testService.writeState();
+
+ assertTrue(sAppOpsFile.exists());
+
+ AppOpsDataParser parser = new AppOpsDataParser(sAppOpsFile);
+ assertTrue(parser.parse());
+ assertEquals(AppOpsServiceImpl.CURRENT_VERSION, parser.mVersion);
+ }
+
+ @Test
+ public void upgradeFromNoVersion() {
+ extractAppOpsFile(APP_OPS_UNVERSIONED_ASSET_PATH);
+ AppOpsDataParser parser = new AppOpsDataParser(sAppOpsFile);
assertTrue(parser.parse());
assertEquals(AppOpsDataParser.NO_VERSION, parser.mVersion);
- // Use mock context and package manager to fake permision package manager calls.
- Context testContext = spy(mContext);
+ AppOpsServiceImpl testService = spy(
+ new AppOpsServiceImpl(sAppOpsFile, mHandler, mTestContext));
- // Pretent everybody has all permissions
- doNothing().when(testContext).enforcePermission(anyString(), anyInt(), anyInt(),
- nullable(String.class));
+ doNothing().when(testService).upgradeRunAnyInBackgroundLocked();
+ doNothing().when(testService).upgradeScheduleExactAlarmLocked();
- PackageManager testPM = mock(PackageManager.class);
- when(testContext.getPackageManager()).thenReturn(testPM);
+ // trigger upgrade
+ testService.systemReady();
- // Stub out package calls to disable AppOpsService#updatePermissionRevokedCompat
- when(testPM.getPackagesForUid(anyInt())).thenReturn(null);
+ verify(testService).upgradeRunAnyInBackgroundLocked();
+ verify(testService).upgradeScheduleExactAlarmLocked();
+
+ testService.writeState();
+ assertTrue(parser.parse());
+ assertEquals(AppOpsServiceImpl.CURRENT_VERSION, parser.mVersion);
+ }
+
+ @Test
+ public void upgradeFromVersion1() {
+ extractAppOpsFile(APP_OPS_VERSION_1_ASSET_PATH);
+ AppOpsDataParser parser = new AppOpsDataParser(sAppOpsFile);
+ assertTrue(parser.parse());
+ assertEquals(1, parser.mVersion);
AppOpsServiceImpl testService = spy(
- new AppOpsServiceImpl(mAppOpsFile, mHandler, testContext)); // trigger upgrade
- assertSameModes(testService.mUidStates, AppOpsManager.OP_RUN_IN_BACKGROUND,
- AppOpsManager.OP_RUN_ANY_IN_BACKGROUND);
- mHandler.removeCallbacks(testService.mWriteRunner);
+ new AppOpsServiceImpl(sAppOpsFile, mHandler, mTestContext));
+
+ doNothing().when(testService).upgradeRunAnyInBackgroundLocked();
+ doNothing().when(testService).upgradeScheduleExactAlarmLocked();
+
+ // trigger upgrade
+ testService.systemReady();
+
+ verify(testService, never()).upgradeRunAnyInBackgroundLocked();
+ verify(testService).upgradeScheduleExactAlarmLocked();
+
testService.writeState();
assertTrue(parser.parse());
- assertEquals(CURRENT_VERSION, parser.mVersion);
+ assertEquals(AppOpsServiceImpl.CURRENT_VERSION, parser.mVersion);
}
/**
@@ -174,7 +352,7 @@ public class AppOpsUpgradeTest {
* Other fields may be added as and when required for testing.
*/
private static final class AppOpsDataParser {
- static final int NO_VERSION = -1;
+ static final int NO_VERSION = -123;
int mVersion;
private File mFile;
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
index 395e6ac1017d..5e5cbdc22a23 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
@@ -23,6 +23,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
@@ -58,6 +59,7 @@ import com.android.server.lights.LogicalLight;
import com.google.common.truth.Truth;
import org.junit.After;
+import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -84,6 +86,8 @@ public class LocalDisplayAdapterTest {
private static final long HANDLER_WAIT_MS = 100;
+ private static final int[] HDR_TYPES = new int[]{1, 2};
+
private StaticMockitoSession mMockitoSession;
private LocalDisplayAdapter mAdapter;
@@ -202,6 +206,38 @@ public class LocalDisplayAdapterTest {
PORT_C, false);
}
+ @Test
+ public void testSupportedDisplayModesGetOverriddenWhenDisplayIsUpdated()
+ throws InterruptedException {
+ SurfaceControl.DisplayMode displayMode = createFakeDisplayMode(0, 1920, 1080, 0);
+ displayMode.supportedHdrTypes = new int[0];
+ FakeDisplay display = new FakeDisplay(PORT_A, new SurfaceControl.DisplayMode[]{displayMode},
+ 0, 0);
+ setUpDisplay(display);
+ updateAvailableDisplays();
+ mAdapter.registerLocked();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+ DisplayDevice displayDevice = mListener.addedDisplays.get(0);
+ displayDevice.applyPendingDisplayDeviceInfoChangesLocked();
+ Display.Mode[] supportedModes = displayDevice.getDisplayDeviceInfoLocked().supportedModes;
+ Assert.assertEquals(1, supportedModes.length);
+ Assert.assertEquals(0, supportedModes[0].getSupportedHdrTypes().length);
+
+ displayMode.supportedHdrTypes = new int[]{3, 2};
+ display.dynamicInfo.supportedDisplayModes = new SurfaceControl.DisplayMode[]{displayMode};
+ setUpDisplay(display);
+ mInjector.getTransmitter().sendHotplug(display, true);
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+ displayDevice = mListener.changedDisplays.get(0);
+ displayDevice.applyPendingDisplayDeviceInfoChangesLocked();
+ supportedModes = displayDevice.getDisplayDeviceInfoLocked().supportedModes;
+
+ Assert.assertEquals(1, supportedModes.length);
+ assertArrayEquals(new int[]{2, 3}, supportedModes[0].getSupportedHdrTypes());
+ }
+
/**
* Confirm that all local displays are public when config_localPrivateDisplayPorts is empty.
*/
@@ -357,7 +393,7 @@ public class LocalDisplayAdapterTest {
SurfaceControl.DisplayMode displayMode = createFakeDisplayMode(0, 1920, 1080, 60f);
SurfaceControl.DisplayMode[] modes =
new SurfaceControl.DisplayMode[]{displayMode};
- FakeDisplay display = new FakeDisplay(PORT_A, modes, 0);
+ FakeDisplay display = new FakeDisplay(PORT_A, modes, 0, displayMode.refreshRate);
setUpDisplay(display);
updateAvailableDisplays();
mAdapter.registerLocked();
@@ -413,7 +449,7 @@ public class LocalDisplayAdapterTest {
SurfaceControl.DisplayMode displayMode = createFakeDisplayMode(0, 1920, 1080, 60f);
SurfaceControl.DisplayMode[] modes =
new SurfaceControl.DisplayMode[]{displayMode};
- FakeDisplay display = new FakeDisplay(PORT_A, modes, 0);
+ FakeDisplay display = new FakeDisplay(PORT_A, modes, 0, displayMode.refreshRate);
setUpDisplay(display);
updateAvailableDisplays();
mAdapter.registerLocked();
@@ -468,7 +504,7 @@ public class LocalDisplayAdapterTest {
createFakeDisplayMode(0, 1920, 1080, 60f),
createFakeDisplayMode(1, 1920, 1080, 50f)
};
- FakeDisplay display = new FakeDisplay(PORT_A, modes, /* activeMode */ 0);
+ FakeDisplay display = new FakeDisplay(PORT_A, modes, /* activeMode */ 0, 60f);
setUpDisplay(display);
updateAvailableDisplays();
mAdapter.registerLocked();
@@ -502,6 +538,49 @@ public class LocalDisplayAdapterTest {
}
@Test
+ public void testAfterDisplayChange_RenderFrameRateIsUpdated() throws Exception {
+ SurfaceControl.DisplayMode[] modes = new SurfaceControl.DisplayMode[]{
+ createFakeDisplayMode(0, 1920, 1080, 60f),
+ };
+ FakeDisplay display = new FakeDisplay(PORT_A, modes, /* activeMode */ 0,
+ /* renderFrameRate */30f);
+ setUpDisplay(display);
+ updateAvailableDisplays();
+ mAdapter.registerLocked();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+ assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+ assertThat(mListener.changedDisplays).isEmpty();
+
+ DisplayDeviceInfo displayDeviceInfo = mListener.addedDisplays.get(0)
+ .getDisplayDeviceInfoLocked();
+
+ Display.Mode activeMode = getModeById(displayDeviceInfo, displayDeviceInfo.modeId);
+ assertThat(activeMode.matches(1920, 1080, 60f)).isTrue();
+ assertEquals(Float.floatToIntBits(30f),
+ Float.floatToIntBits(displayDeviceInfo.renderFrameRate));
+
+ // Change the render frame rate
+ display.dynamicInfo.renderFrameRate = 60f;
+ setUpDisplay(display);
+ mInjector.getTransmitter().sendHotplug(display, /* connected */ true);
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+ assertTrue(mListener.traversalRequested);
+ assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+ assertThat(mListener.changedDisplays.size()).isEqualTo(1);
+
+ DisplayDevice displayDevice = mListener.changedDisplays.get(0);
+ displayDevice.applyPendingDisplayDeviceInfoChangesLocked();
+ displayDeviceInfo = displayDevice.getDisplayDeviceInfoLocked();
+
+ activeMode = getModeById(displayDeviceInfo, displayDeviceInfo.modeId);
+ assertThat(activeMode.matches(1920, 1080, 60f)).isTrue();
+ assertEquals(Float.floatToIntBits(60f),
+ Float.floatToIntBits(displayDeviceInfo.renderFrameRate));
+ }
+
+ @Test
public void testAfterDisplayChange_HdrCapabilitiesAreUpdated() throws Exception {
FakeDisplay display = new FakeDisplay(PORT_A);
Display.HdrCapabilities initialHdrCapabilities = new Display.HdrCapabilities(new int[0],
@@ -686,7 +765,7 @@ public class LocalDisplayAdapterTest {
createFakeDisplayMode(1, 1920, 1080, 50f)
};
final int activeMode = 0;
- FakeDisplay display = new FakeDisplay(PORT_A, modes, activeMode);
+ FakeDisplay display = new FakeDisplay(PORT_A, modes, activeMode, 60f);
display.desiredDisplayModeSpecs.defaultMode = 1;
setUpDisplay(display);
@@ -943,11 +1022,13 @@ public class LocalDisplayAdapterTest {
dynamicInfo.activeDisplayModeId = 0;
}
- private FakeDisplay(int port, SurfaceControl.DisplayMode[] modes, int activeMode) {
+ private FakeDisplay(int port, SurfaceControl.DisplayMode[] modes, int activeMode,
+ float renderFrameRate) {
address = createDisplayAddress(port);
info = createFakeDisplayInfo();
dynamicInfo.supportedDisplayModes = modes;
dynamicInfo.activeDisplayModeId = activeMode;
+ dynamicInfo.renderFrameRate = renderFrameRate;
}
private FakeDisplay(int port, SurfaceControl.DisplayMode[] modes, int activeMode,
@@ -965,9 +1046,9 @@ public class LocalDisplayAdapterTest {
mAddresses.add(display.address);
when(mSurfaceControlProxy.getPhysicalDisplayToken(display.address.getPhysicalDisplayId()))
.thenReturn(display.token);
- when(mSurfaceControlProxy.getStaticDisplayInfo(display.token))
+ when(mSurfaceControlProxy.getStaticDisplayInfo(display.address.getPhysicalDisplayId()))
.thenReturn(display.info);
- when(mSurfaceControlProxy.getDynamicDisplayInfo(display.token))
+ when(mSurfaceControlProxy.getDynamicDisplayInfo(display.address.getPhysicalDisplayId()))
.thenReturn(display.dynamicInfo);
when(mSurfaceControlProxy.getDesiredDisplayModeSpecs(display.token))
.thenReturn(display.desiredDisplayModeSpecs);
@@ -1008,6 +1089,7 @@ public class LocalDisplayAdapterTest {
mode.xDpi = 100;
mode.yDpi = 100;
mode.group = group;
+ mode.supportedHdrTypes = HDR_TYPES;
return mode;
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
index fc737d06059d..8e4849043984 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
@@ -34,6 +34,8 @@ import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;
import android.app.ActivityManager;
@@ -46,6 +48,7 @@ import android.app.job.JobScheduler;
import android.app.usage.UsageStatsManagerInternal;
import android.content.ComponentName;
import android.content.Context;
+import android.content.PermissionChecker;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.res.Resources;
@@ -63,6 +66,7 @@ import com.android.server.DeviceIdleInternal;
import com.android.server.LocalServices;
import com.android.server.PowerAllowlistInternal;
import com.android.server.SystemServiceManager;
+import com.android.server.job.controllers.ConnectivityController;
import com.android.server.job.controllers.JobStatus;
import com.android.server.pm.UserManagerInternal;
import com.android.server.usage.AppStandbyInternal;
@@ -102,6 +106,7 @@ public class JobSchedulerServiceTest {
.initMocks(this)
.strictness(Strictness.LENIENT)
.mockStatic(LocalServices.class)
+ .mockStatic(PermissionChecker.class)
.mockStatic(ServiceManager.class)
.startMocking();
@@ -193,6 +198,15 @@ public class JobSchedulerServiceTest {
jobInfoBuilder.build(), callingUid, "com.android.test", 0, testTag);
}
+ private void grantRunLongJobsPermission(boolean grant) {
+ final int permissionStatus = grant
+ ? PermissionChecker.PERMISSION_GRANTED : PermissionChecker.PERMISSION_HARD_DENIED;
+ doReturn(permissionStatus)
+ .when(() -> PermissionChecker.checkPermissionForPreflight(
+ any(), eq(android.Manifest.permission.RUN_LONG_JOBS),
+ anyInt(), anyInt(), anyString()));
+ }
+
@Test
public void testGetMinJobExecutionGuaranteeMs() {
JobStatus ejMax = createJobStatus("testGetMinJobExecutionGuaranteeMs",
@@ -207,6 +221,15 @@ public class JobSchedulerServiceTest {
createJobInfo(5).setPriority(JobInfo.PRIORITY_HIGH));
JobStatus jobDef = createJobStatus("testGetMinJobExecutionGuaranteeMs",
createJobInfo(6));
+ JobStatus jobDT = createJobStatus("testGetMinJobExecutionGuaranteeMs",
+ createJobInfo(7)
+ .setDataTransfer(true).setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
+ JobStatus jobUI = createJobStatus("testGetMinJobExecutionGuaranteeMs",
+ createJobInfo(8)); // TODO(255371817): add setUserInitiated(true)
+ JobStatus jobUIDT = createJobStatus("testGetMinJobExecutionGuaranteeMs",
+ // TODO(255371817): add setUserInitiated(true)
+ createJobInfo(9)
+ .setDataTransfer(true).setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
spyOn(ejMax);
spyOn(ejHigh);
@@ -214,6 +237,9 @@ public class JobSchedulerServiceTest {
spyOn(ejHighDowngraded);
spyOn(jobHigh);
spyOn(jobDef);
+ spyOn(jobDT);
+ spyOn(jobUI);
+ spyOn(jobUIDT);
when(ejMax.shouldTreatAsExpeditedJob()).thenReturn(true);
when(ejHigh.shouldTreatAsExpeditedJob()).thenReturn(true);
@@ -221,6 +247,16 @@ public class JobSchedulerServiceTest {
when(ejHighDowngraded.shouldTreatAsExpeditedJob()).thenReturn(false);
when(jobHigh.shouldTreatAsExpeditedJob()).thenReturn(false);
when(jobDef.shouldTreatAsExpeditedJob()).thenReturn(false);
+ when(jobUI.shouldTreatAsUserInitiated()).thenReturn(true);
+ when(jobUIDT.shouldTreatAsUserInitiated()).thenReturn(true);
+
+ ConnectivityController connectivityController = mService.getConnectivityController();
+ spyOn(connectivityController);
+ mService.mConstants.RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS = 10 * MINUTE_IN_MILLIS;
+ mService.mConstants.RUNTIME_DATA_TRANSFER_LIMIT_MS = 60 * MINUTE_IN_MILLIS;
+ mService.mConstants.RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR = 1.5f;
+ mService.mConstants.RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS = HOUR_IN_MILLIS;
+ mService.mConstants.RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS = 6 * HOUR_IN_MILLIS;
assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(ejMax));
@@ -234,8 +270,81 @@ public class JobSchedulerServiceTest {
mService.getMinJobExecutionGuaranteeMs(jobHigh));
assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(jobDef));
+ grantRunLongJobsPermission(false); // Without permission
+ assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobDT));
+ grantRunLongJobsPermission(true); // With permission
+ doReturn(ConnectivityController.UNKNOWN_TIME)
+ .when(connectivityController).getEstimatedTransferTimeMs(any());
+ assertEquals(mService.mConstants.RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobDT));
+ doReturn(mService.mConstants.RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS / 2)
+ .when(connectivityController).getEstimatedTransferTimeMs(any());
+ assertEquals(mService.mConstants.RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobDT));
+ doReturn(mService.mConstants.RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS * 2)
+ .when(connectivityController).getEstimatedTransferTimeMs(any());
+ assertEquals(mService.mConstants.RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobDT));
+ doReturn(mService.mConstants.RUNTIME_DATA_TRANSFER_LIMIT_MS * 2)
+ .when(connectivityController).getEstimatedTransferTimeMs(any());
+ assertEquals(mService.mConstants.RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobDT));
+ // UserInitiated
+ assertEquals(mService.mConstants.RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobUI));
+ grantRunLongJobsPermission(false);
+ assertEquals(mService.mConstants.RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobUIDT));
+ grantRunLongJobsPermission(true); // With permission
+ doReturn(ConnectivityController.UNKNOWN_TIME)
+ .when(connectivityController).getEstimatedTransferTimeMs(any());
+ assertEquals(mService.mConstants.RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobUIDT));
+ doReturn(mService.mConstants.RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS / 2)
+ .when(connectivityController).getEstimatedTransferTimeMs(any());
+ assertEquals(mService.mConstants.RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobUIDT));
+ doReturn(mService.mConstants.RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS * 2)
+ .when(connectivityController).getEstimatedTransferTimeMs(any());
+ assertEquals(
+ (long) (mService.mConstants.RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS
+ * 2 * 1.5),
+ mService.getMinJobExecutionGuaranteeMs(jobUIDT));
+ doReturn(mService.mConstants.RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS * 2)
+ .when(connectivityController).getEstimatedTransferTimeMs(any());
+ assertEquals(mService.mConstants.RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobUIDT));
}
+ @Test
+ public void testGetMaxJobExecutionTimeMs() {
+ JobStatus jobDT = createJobStatus("testGetMaxJobExecutionTimeMs",
+ createJobInfo(7)
+ .setDataTransfer(true).setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
+ JobStatus jobUI = createJobStatus("testGetMaxJobExecutionTimeMs",
+ createJobInfo(9)); // TODO(255371817): add setUserInitiated(true)
+ JobStatus jobUIDT = createJobStatus("testGetMaxJobExecutionTimeMs",
+ // TODO(255371817): add setUserInitiated(true)
+ createJobInfo(10)
+ .setDataTransfer(true).setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
+
+ spyOn(jobDT);
+ spyOn(jobUI);
+ spyOn(jobUIDT);
+
+ when(jobUI.shouldTreatAsUserInitiated()).thenReturn(true);
+ when(jobUIDT.shouldTreatAsUserInitiated()).thenReturn(true);
+
+ grantRunLongJobsPermission(true);
+
+ assertEquals(mService.mConstants.RUNTIME_DATA_TRANSFER_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobDT));
+ assertEquals(mService.mConstants.RUNTIME_USER_INITIATED_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobUI));
+ assertEquals(mService.mConstants.RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobUIDT));
+ }
/**
* Confirm that {@link JobSchedulerService#getRescheduleJobForFailureLocked(JobStatus, int)}
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
index 1f85f2c2c46e..42e22f3f7c2f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
@@ -24,6 +24,7 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_VPN;
+import static android.text.format.DateUtils.SECOND_IN_MILLIS;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -35,6 +36,7 @@ import static com.android.server.job.JobSchedulerService.FREQUENT_INDEX;
import static com.android.server.job.JobSchedulerService.RARE_INDEX;
import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
@@ -240,20 +242,20 @@ public class ConnectivityControllerTest {
.setLinkDownstreamBandwidthKbps(1).build(), mConstants));
// Slow downstream
assertFalse(controller.isSatisfied(createJobStatus(job), net,
- createCapabilitiesBuilder().setLinkUpstreamBandwidthKbps(137)
+ createCapabilitiesBuilder().setLinkUpstreamBandwidthKbps(140)
.setLinkDownstreamBandwidthKbps(1).build(), mConstants));
// Slow upstream
assertFalse(controller.isSatisfied(createJobStatus(job), net,
createCapabilitiesBuilder().setLinkUpstreamBandwidthKbps(1)
- .setLinkDownstreamBandwidthKbps(137).build(), mConstants));
+ .setLinkDownstreamBandwidthKbps(140).build(), mConstants));
// Network good enough
assertTrue(controller.isSatisfied(createJobStatus(job), net,
- createCapabilitiesBuilder().setLinkUpstreamBandwidthKbps(137)
- .setLinkDownstreamBandwidthKbps(137).build(), mConstants));
+ createCapabilitiesBuilder().setLinkUpstreamBandwidthKbps(140)
+ .setLinkDownstreamBandwidthKbps(140).build(), mConstants));
// Network slightly too slow given reduced time
assertFalse(controller.isSatisfied(createJobStatus(job), net,
- createCapabilitiesBuilder().setLinkUpstreamBandwidthKbps(130)
- .setLinkDownstreamBandwidthKbps(130).build(), mConstants));
+ createCapabilitiesBuilder().setLinkUpstreamBandwidthKbps(139)
+ .setLinkDownstreamBandwidthKbps(139).build(), mConstants));
// Slow network is too slow, but device is charging and network is unmetered.
when(mService.isBatteryCharging()).thenReturn(true);
controller.onBatteryStateChangedLocked();
@@ -1188,6 +1190,78 @@ public class ConnectivityControllerTest {
assertFalse(unnetworked.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
}
+ @Test
+ public void testCalculateTransferTimeMs() {
+ assertEquals(ConnectivityController.UNKNOWN_TIME,
+ ConnectivityController.calculateTransferTimeMs(1, 0));
+ assertEquals(ConnectivityController.UNKNOWN_TIME,
+ ConnectivityController.calculateTransferTimeMs(JobInfo.NETWORK_BYTES_UNKNOWN, 512));
+ assertEquals(1, ConnectivityController.calculateTransferTimeMs(1, 8));
+ assertEquals(1000, ConnectivityController.calculateTransferTimeMs(1000, 8));
+ assertEquals(8, ConnectivityController.calculateTransferTimeMs(1024, 1024));
+ }
+
+ @Test
+ public void testGetEstimatedTransferTimeMs() {
+ final ArgumentCaptor<NetworkCallback> callbackCaptor =
+ ArgumentCaptor.forClass(NetworkCallback.class);
+ doNothing().when(mConnManager).registerNetworkCallback(any(), callbackCaptor.capture());
+
+ final JobStatus job = createJobStatus(createJob()
+ .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(10_000),
+ DataUnit.MEBIBYTES.toBytes(1_000))
+ .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
+
+ final ConnectivityController controller = new ConnectivityController(mService,
+ mFlexibilityController);
+
+ final JobStatus jobNoEstimates = createJobStatus(createJob());
+ assertEquals(ConnectivityController.UNKNOWN_TIME,
+ controller.getEstimatedTransferTimeMs(jobNoEstimates));
+
+ // No network
+ job.network = null;
+ assertEquals(ConnectivityController.UNKNOWN_TIME,
+ controller.getEstimatedTransferTimeMs(job));
+
+ final NetworkCallback generalCallback = callbackCaptor.getValue();
+
+ // No capabilities
+ final Network network = mock(Network.class);
+ answerNetwork(generalCallback, null, null, network, null);
+ job.network = network;
+ assertEquals(ConnectivityController.UNKNOWN_TIME,
+ controller.getEstimatedTransferTimeMs(job));
+
+ // Capabilities don't have bandwidth values
+ NetworkCapabilities caps = createCapabilitiesBuilder().build();
+ answerNetwork(generalCallback, null, null, network, caps);
+ assertEquals(ConnectivityController.UNKNOWN_TIME,
+ controller.getEstimatedTransferTimeMs(job));
+
+ // Capabilities only has downstream bandwidth
+ caps = createCapabilitiesBuilder()
+ .setLinkDownstreamBandwidthKbps(1024)
+ .build();
+ answerNetwork(generalCallback, null, null, network, caps);
+ assertEquals(81920 * SECOND_IN_MILLIS, controller.getEstimatedTransferTimeMs(job));
+
+ // Capabilities only has upstream bandwidth
+ caps = createCapabilitiesBuilder()
+ .setLinkUpstreamBandwidthKbps(2 * 1024)
+ .build();
+ answerNetwork(generalCallback, null, null, network, caps);
+ assertEquals(4096 * SECOND_IN_MILLIS, controller.getEstimatedTransferTimeMs(job));
+
+ // Capabilities only both stream bandwidths
+ caps = createCapabilitiesBuilder()
+ .setLinkDownstreamBandwidthKbps(1024)
+ .setLinkUpstreamBandwidthKbps(2 * 1024)
+ .build();
+ answerNetwork(generalCallback, null, null, network, caps);
+ assertEquals((81920 + 4096) * SECOND_IN_MILLIS, controller.getEstimatedTransferTimeMs(job));
+ }
+
private void answerNetwork(@NonNull NetworkCallback generalCallback,
@Nullable NetworkCallback uidCallback, @Nullable Network lastNetwork,
@Nullable Network net, @Nullable NetworkCapabilities caps) {
@@ -1198,11 +1272,15 @@ public class ConnectivityControllerTest {
}
} else {
generalCallback.onAvailable(net);
- generalCallback.onCapabilitiesChanged(net, caps);
+ if (caps != null) {
+ generalCallback.onCapabilitiesChanged(net, caps);
+ }
if (uidCallback != null) {
uidCallback.onAvailable(net);
uidCallback.onBlockedStatusChanged(net, ConnectivityManager.BLOCKED_REASON_NONE);
- uidCallback.onCapabilitiesChanged(net, caps);
+ if (caps != null) {
+ uidCallback.onCapabilitiesChanged(net, caps);
+ }
}
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceOrInternalTestCase.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceOrInternalTestCase.java
deleted file mode 100644
index 6c85b7ab4985..000000000000
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceOrInternalTestCase.java
+++ /dev/null
@@ -1,213 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.server.pm;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.when;
-
-import android.annotation.UserIdInt;
-import android.app.ActivityManagerInternal;
-import android.content.Context;
-import android.content.pm.UserInfo;
-import android.os.UserManager;
-import android.util.Log;
-import android.util.SparseArray;
-
-import androidx.test.annotation.UiThreadTest;
-
-import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
-import com.android.server.ExtendedMockitoTestCase;
-import com.android.server.LocalServices;
-import com.android.server.am.UserState;
-import com.android.server.pm.UserManagerService.UserData;
-
-import org.junit.After;
-import org.junit.Before;
-import org.mockito.Mock;
-
-/**
- * Base class for {@link UserManagerInternalTest} and {@link UserManagerInternalTest}.
- *
- * <p>{@link UserManagerService} and its {@link UserManagerInternal} implementation have a
- * "symbiotic relationship - some methods of the former simply call the latter and vice versa.
- *
- * <p>Ideally, only one of them should have the logic, but since that's not the case, this class
- * provides the infra to make it easier to test both (which in turn would make it easier / safer to
- * refactor their logic later).
- */
-// TODO(b/244644281): there is no UserManagerInternalTest anymore as the logic being tested there
-// moved to UserVisibilityController, so it might be simpler to merge this class into
-// UserManagerServiceTest (once the UserVisibilityController -> UserManagerService dependency is
-// fixed)
-abstract class UserManagerServiceOrInternalTestCase extends ExtendedMockitoTestCase {
-
- private static final String TAG = UserManagerServiceOrInternalTestCase.class.getSimpleName();
-
- /**
- * Id for a simple user (that doesn't have profiles).
- */
- protected static final int USER_ID = 600;
-
- /**
- * Id for another simple user.
- */
- protected static final int OTHER_USER_ID = 666;
-
- /**
- * Id for a user that has one profile (whose id is {@link #PROFILE_USER_ID}.
- *
- * <p>You can use {@link #addDefaultProfileAndParent()} to add both of this user to the service.
- */
- protected static final int PARENT_USER_ID = 642;
-
- /**
- * Id for a profile whose parent is {@link #PARENTUSER_ID}.
- *
- * <p>You can use {@link #addDefaultProfileAndParent()} to add both of this user to the service.
- */
- protected static final int PROFILE_USER_ID = 643;
-
- private final Object mPackagesLock = new Object();
- private final Context mRealContext = androidx.test.InstrumentationRegistry.getInstrumentation()
- .getTargetContext();
- private final SparseArray<UserData> mUsers = new SparseArray<>();
-
- private Context mSpiedContext;
-
- private @Mock PackageManagerService mMockPms;
- private @Mock UserDataPreparer mMockUserDataPreparer;
- private @Mock ActivityManagerInternal mActivityManagerInternal;
-
- /**
- * Reference to the {@link UserManagerService} being tested.
- */
- protected UserManagerService mUms;
-
- /**
- * Reference to the {@link UserManagerInternal} being tested.
- */
- protected UserManagerInternal mUmi;
-
- @Override
- protected void initializeSession(StaticMockitoSessionBuilder builder) {
- builder
- .spyStatic(UserManager.class)
- .spyStatic(LocalServices.class);
- }
-
- @Before
- @UiThreadTest // Needed to initialize main handler
- public final void setFixtures() {
- mSpiedContext = spy(mRealContext);
-
- // Called when WatchedUserStates is constructed
- doNothing().when(() -> UserManager.invalidateIsUserUnlockedCache());
-
- // Must construct UserManagerService in the UiThread
- mUms = new UserManagerService(mSpiedContext, mMockPms, mMockUserDataPreparer,
- mPackagesLock, mRealContext.getDataDir(), mUsers);
- mUmi = LocalServices.getService(UserManagerInternal.class);
- assertWithMessage("LocalServices.getService(UserManagerInternal.class)").that(mUmi)
- .isNotNull();
- }
-
- @After
- public final void resetUserManagerInternal() {
- // LocalServices follows the "Highlander rule" - There can be only one!
- LocalServices.removeServiceForTest(UserManagerInternal.class);
- }
-
- ///////////////////////////////////////////
- // Helper methods exposed to sub-classes //
- ///////////////////////////////////////////
-
- protected final void mockCurrentUser(@UserIdInt int userId) {
- mockGetLocalService(ActivityManagerInternal.class, mActivityManagerInternal);
-
- when(mActivityManagerInternal.getCurrentUserId()).thenReturn(userId);
- }
-
- protected final <T> void mockGetLocalService(Class<T> serviceClass, T service) {
- doReturn(service).when(() -> LocalServices.getService(serviceClass));
- }
-
- protected final void addDefaultProfileAndParent() {
- addUser(PARENT_USER_ID);
- addProfile(PROFILE_USER_ID, PARENT_USER_ID);
- }
-
- protected final void addProfile(@UserIdInt int profileId, @UserIdInt int parentId) {
- TestUserData profileData = new TestUserData(profileId);
- profileData.info.flags = UserInfo.FLAG_PROFILE;
- profileData.info.profileGroupId = parentId;
-
- addUserData(profileData);
- }
-
- protected final void addUser(@UserIdInt int userId) {
- TestUserData userData = new TestUserData(userId);
-
- addUserData(userData);
- }
-
- protected final void startDefaultProfile() {
- startUser(PROFILE_USER_ID);
- }
-
- protected final void stopDefaultProfile() {
- stopUser(PROFILE_USER_ID);
- }
-
- protected final void startUser(@UserIdInt int userId) {
- setUserState(userId, UserState.STATE_RUNNING_UNLOCKED);
- }
-
- protected final void stopUser(@UserIdInt int userId) {
- setUserState(userId, UserState.STATE_STOPPING);
- }
-
- protected final void setUserState(@UserIdInt int userId, int userState) {
- mUmi.setUserState(userId, userState);
- }
-
- ///////////////////
- // Private infra //
- ///////////////////
-
- private void addUserData(TestUserData userData) {
- Log.d(TAG, "Adding " + userData);
- mUsers.put(userData.info.id, userData);
- }
-
- private static final class TestUserData extends UserData {
-
- @SuppressWarnings("deprecation")
- TestUserData(@UserIdInt int userId) {
- info = new UserInfo();
- info.id = userId;
- }
-
- @Override
- public String toString() {
- return "TestUserData[" + info.toFullString() + "]";
- }
- }
-}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
index b5ffe5f24338..1367af8eede3 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -15,17 +15,116 @@
*/
package com.android.server.pm;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+
import static com.google.common.truth.Truth.assertWithMessage;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.annotation.UserIdInt;
import android.app.ActivityManagerInternal;
+import android.content.Context;
+import android.content.pm.UserInfo;
import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.Log;
+import android.util.SparseArray;
+
+import androidx.test.annotation.UiThreadTest;
+
+import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
+import com.android.server.ExtendedMockitoTestCase;
+import com.android.server.LocalServices;
+import com.android.server.am.UserState;
+import com.android.server.pm.UserManagerService.UserData;
+import org.junit.After;
+import org.junit.Before;
import org.junit.Test;
+import org.mockito.Mock;
/**
* Run as {@code atest FrameworksMockingServicesTests:com.android.server.pm.UserManagerServiceTest}
*/
-public final class UserManagerServiceTest extends UserManagerServiceOrInternalTestCase {
+public final class UserManagerServiceTest extends ExtendedMockitoTestCase {
+
+ private static final String TAG = UserManagerServiceTest.class.getSimpleName();
+
+ /**
+ * Id for a simple user (that doesn't have profiles).
+ */
+ private static final int USER_ID = 600;
+
+ /**
+ * Id for another simple user.
+ */
+ private static final int OTHER_USER_ID = 666;
+
+ /**
+ * Id for a user that has one profile (whose id is {@link #PROFILE_USER_ID}.
+ *
+ * <p>You can use {@link #addDefaultProfileAndParent()} to add both of this user to the service.
+ */
+ private static final int PARENT_USER_ID = 642;
+
+ /**
+ * Id for a profile whose parent is {@link #PARENTUSER_ID}.
+ *
+ * <p>You can use {@link #addDefaultProfileAndParent()} to add both of this user to the service.
+ */
+ private static final int PROFILE_USER_ID = 643;
+
+ private final Object mPackagesLock = new Object();
+ private final Context mRealContext = androidx.test.InstrumentationRegistry.getInstrumentation()
+ .getTargetContext();
+ private final SparseArray<UserData> mUsers = new SparseArray<>();
+
+ private Context mSpiedContext;
+
+ private @Mock PackageManagerService mMockPms;
+ private @Mock UserDataPreparer mMockUserDataPreparer;
+ private @Mock ActivityManagerInternal mActivityManagerInternal;
+
+ /**
+ * Reference to the {@link UserManagerService} being tested.
+ */
+ private UserManagerService mUms;
+
+ /**
+ * Reference to the {@link UserManagerInternal} being tested.
+ */
+ private UserManagerInternal mUmi;
+
+ @Override
+ protected void initializeSession(StaticMockitoSessionBuilder builder) {
+ builder
+ .spyStatic(UserManager.class)
+ .spyStatic(LocalServices.class);
+ }
+
+ @Before
+ @UiThreadTest // Needed to initialize main handler
+ public void setFixtures() {
+ mSpiedContext = spy(mRealContext);
+
+ // Called when WatchedUserStates is constructed
+ doNothing().when(() -> UserManager.invalidateIsUserUnlockedCache());
+
+ // Must construct UserManagerService in the UiThread
+ mUms = new UserManagerService(mSpiedContext, mMockPms, mMockUserDataPreparer,
+ mPackagesLock, mRealContext.getDataDir(), mUsers);
+ mUmi = LocalServices.getService(UserManagerInternal.class);
+ assertWithMessage("LocalServices.getService(UserManagerInternal.class)").that(mUmi)
+ .isNotNull();
+ }
+
+ @After
+ public void resetUserManagerInternal() {
+ // LocalServices follows the "Highlander rule" - There can be only one!
+ LocalServices.removeServiceForTest(UserManagerInternal.class);
+ }
@Test
public void testGetCurrentUserId_amInternalNotReady() {
@@ -123,4 +222,72 @@ public final class UserManagerServiceTest extends UserManagerServiceOrInternalTe
assertWithMessage("isUserRunning(%s)", PROFILE_USER_ID)
.that(mUms.isUserRunning(PROFILE_USER_ID)).isFalse();
}
+
+ private void mockCurrentUser(@UserIdInt int userId) {
+ mockGetLocalService(ActivityManagerInternal.class, mActivityManagerInternal);
+
+ when(mActivityManagerInternal.getCurrentUserId()).thenReturn(userId);
+ }
+
+ private <T> void mockGetLocalService(Class<T> serviceClass, T service) {
+ doReturn(service).when(() -> LocalServices.getService(serviceClass));
+ }
+
+ private void addDefaultProfileAndParent() {
+ addUser(PARENT_USER_ID);
+ addProfile(PROFILE_USER_ID, PARENT_USER_ID);
+ }
+
+ private void addProfile(@UserIdInt int profileId, @UserIdInt int parentId) {
+ TestUserData profileData = new TestUserData(profileId);
+ profileData.info.flags = UserInfo.FLAG_PROFILE;
+ profileData.info.profileGroupId = parentId;
+
+ addUserData(profileData);
+ }
+
+ private void addUser(@UserIdInt int userId) {
+ TestUserData userData = new TestUserData(userId);
+
+ addUserData(userData);
+ }
+
+ private void startDefaultProfile() {
+ startUser(PROFILE_USER_ID);
+ }
+
+ private void stopDefaultProfile() {
+ stopUser(PROFILE_USER_ID);
+ }
+
+ private void startUser(@UserIdInt int userId) {
+ setUserState(userId, UserState.STATE_RUNNING_UNLOCKED);
+ }
+
+ private void stopUser(@UserIdInt int userId) {
+ setUserState(userId, UserState.STATE_STOPPING);
+ }
+
+ private void setUserState(@UserIdInt int userId, int userState) {
+ mUmi.setUserState(userId, userState);
+ }
+
+ private void addUserData(TestUserData userData) {
+ Log.d(TAG, "Adding " + userData);
+ mUsers.put(userData.info.id, userData);
+ }
+
+ private static final class TestUserData extends UserData {
+
+ @SuppressWarnings("deprecation")
+ TestUserData(@UserIdInt int userId) {
+ info = new UserInfo();
+ info.id = userId;
+ }
+
+ @Override
+ public String toString() {
+ return "TestUserData[" + info.toFullString() + "]";
+ }
+ }
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/power/ScreenUndimDetectorTest.java b/services/tests/mockingservicestests/src/com/android/server/power/ScreenUndimDetectorTest.java
index 234d70b98580..93a1f30bbcf0 100644
--- a/services/tests/mockingservicestests/src/com/android/server/power/ScreenUndimDetectorTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/power/ScreenUndimDetectorTest.java
@@ -20,7 +20,6 @@ import static android.hardware.display.DisplayManagerInternal.DisplayPowerReques
import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_DIM;
import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE;
import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_OFF;
-import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_VR;
import static android.provider.DeviceConfig.NAMESPACE_ATTENTION_MANAGER_SERVICE;
import static android.view.Display.DEFAULT_DISPLAY_GROUP;
@@ -59,8 +58,7 @@ public class ScreenUndimDetectorTest {
Arrays.asList(POLICY_OFF,
POLICY_DOZE,
POLICY_DIM,
- POLICY_BRIGHT,
- POLICY_VR);
+ POLICY_BRIGHT);
private static final int OTHER_DISPLAY_GROUP = DEFAULT_DISPLAY_GROUP + 1;
@ClassRule
@@ -291,7 +289,7 @@ public class ScreenUndimDetectorTest {
@Test
public void recordScreenPolicy_dimToNonBright_resets() {
- for (int to : Arrays.asList(POLICY_OFF, POLICY_DOZE, POLICY_VR)) {
+ for (int to : Arrays.asList(POLICY_OFF, POLICY_DOZE)) {
setup();
mScreenUndimDetector.mUndimCounter = 1;
mScreenUndimDetector.mUndimCounterStartedMillis = 123;
@@ -309,7 +307,7 @@ public class ScreenUndimDetectorTest {
@Test
public void recordScreenPolicy_brightToNonDim_resets() {
- for (int to : Arrays.asList(POLICY_OFF, POLICY_DOZE, POLICY_VR)) {
+ for (int to : Arrays.asList(POLICY_OFF, POLICY_DOZE)) {
setup();
mScreenUndimDetector.mUndimCounter = 1;
mScreenUndimDetector.mUndimCounterStartedMillis = 123;
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index 9eb3b92abc6f..71492656913b 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -98,6 +98,7 @@
<uses-permission android:name="android.permission.MAINLINE_NETWORK_STACK"/>
<uses-permission
android:name="android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD"/>
+ <uses-permission android:name="android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG"/>
<uses-permission android:name="android.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY" />
<uses-permission android:name="android.permission.READ_NEARBY_STREAMING_POLICY" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_ROUTING" />
diff --git a/services/tests/servicestests/res/xml/power_profile_test_legacy_modem.xml b/services/tests/servicestests/res/xml/power_profile_test_legacy_modem.xml
new file mode 100644
index 000000000000..5335f9640738
--- /dev/null
+++ b/services/tests/servicestests/res/xml/power_profile_test_legacy_modem.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<device name="Android">
+ <!-- Cellular modem related values. These constants are deprecated, but still supported and
+ need to be tested -->
+ <item name="radio.scanning">720</item>
+ <item name="modem.controller.sleep">70</item>
+ <item name="modem.controller.idle">360</item>
+ <item name="modem.controller.rx">1440</item>
+ <array name="modem.controller.tx"> <!-- Strength 0 to 4 -->
+ <value>720</value>
+ <value>1080</value>
+ <value>1440</value>
+ <value>1800</value>
+ <value>2160</value>
+ </array>
+</device> \ No newline at end of file
diff --git a/services/tests/servicestests/res/xml/power_profile_test_modem_calculator.xml b/services/tests/servicestests/res/xml/power_profile_test_modem_calculator.xml
new file mode 100644
index 000000000000..f57bc0f70b5d
--- /dev/null
+++ b/services/tests/servicestests/res/xml/power_profile_test_modem_calculator.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<device name="Android">
+ <modem>
+ <sleep>70</sleep>
+ <idle>360</idle>
+ <active rat="DEFAULT">
+ <receive>1440</receive>
+ <transmit level="0">720</transmit>
+ <transmit level="1">1080</transmit>
+ <transmit level="2">1440</transmit>
+ <transmit level="3">1800</transmit>
+ <transmit level="4">2160</transmit>
+ </active>
+ </modem>
+</device> \ No newline at end of file
diff --git a/services/tests/servicestests/res/xml/power_profile_test_modem_calculator_multiactive.xml b/services/tests/servicestests/res/xml/power_profile_test_modem_calculator_multiactive.xml
new file mode 100644
index 000000000000..4f5e674c60f5
--- /dev/null
+++ b/services/tests/servicestests/res/xml/power_profile_test_modem_calculator_multiactive.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<device name="Android">
+ <modem>
+ <sleep>70</sleep>
+ <idle>360</idle>
+ <active rat="DEFAULT">
+ <receive>1440</receive>
+ <transmit level="0">720</transmit>
+ <transmit level="1">1080</transmit>
+ <transmit level="2">1440</transmit>
+ <transmit level="3">1800</transmit>
+ <transmit level="4">2160</transmit>
+ </active>
+ <active rat="LTE">
+ <receive>2000</receive>
+ <transmit level="0">800</transmit>
+ <transmit level="1">1200</transmit>
+ <transmit level="2">1600</transmit>
+ <transmit level="3">2000</transmit>
+ <transmit level="4">2400</transmit>
+ </active>
+ <active rat="NR" nrFrequency="DEFAULT">
+ <receive>2222</receive>
+ <transmit level="0">999</transmit>
+ <transmit level="1">1333</transmit>
+ <transmit level="2">1888</transmit>
+ <transmit level="3">2222</transmit>
+ <transmit level="4">2666</transmit>
+ </active>
+ <active rat="NR" nrFrequency="HIGH">
+ <receive>2727</receive>
+ <transmit level="0">1818</transmit>
+ <transmit level="1">2727</transmit>
+ <transmit level="2">3636</transmit>
+ <transmit level="3">4545</transmit>
+ <transmit level="4">5454</transmit>
+ </active>
+ <active rat="NR" nrFrequency="MMWAVE">
+ <receive>3456</receive>
+ <transmit level="0">2345</transmit>
+ <transmit level="1">3456</transmit>
+ <transmit level="2">4567</transmit>
+ <transmit level="3">5678</transmit>
+ <transmit level="4">6789</transmit>
+ </active>
+ </modem>
+</device> \ No newline at end of file
diff --git a/services/tests/servicestests/res/xml/power_profile_test_modem_default.xml b/services/tests/servicestests/res/xml/power_profile_test_modem_default.xml
new file mode 100644
index 000000000000..ab016fbb1f5d
--- /dev/null
+++ b/services/tests/servicestests/res/xml/power_profile_test_modem_default.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<device name="Android">
+ <modem>
+ <!-- Modem sleep drain current value in mA. -->
+ <sleep>10</sleep>
+ <!-- Modem idle drain current value in mA. -->
+ <idle>20</idle>
+ <active rat="DEFAULT">
+ <!-- Transmit current drain in mA. -->
+ <receive>30</receive>
+ <!-- Transmit current drains in mA. Must be defined for all levels (0 to 4) -->
+ <transmit level="0">40</transmit>
+ <transmit level="1">50</transmit>
+ <transmit level="2">60</transmit>
+ <transmit level="3">70</transmit>
+ <transmit level="4">80</transmit>
+ </active>
+ </modem>
+</device> \ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/NetworkManagementServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkManagementServiceTest.java
new file mode 100644
index 000000000000..17a587603009
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/NetworkManagementServiceTest.java
@@ -0,0 +1,368 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_POWERSAVE;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_RESTRICTED;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_STANDBY;
+import static android.util.DebugUtils.valueToString;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+import android.annotation.NonNull;
+import android.content.AttributionSource;
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.INetd;
+import android.net.INetdUnsolicitedEventListener;
+import android.net.LinkAddress;
+import android.net.NetworkPolicyManager;
+import android.os.BatteryStats;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.PermissionEnforcer;
+import android.os.Process;
+import android.os.RemoteException;
+import android.permission.PermissionCheckerManager;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.ArrayMap;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.app.IBatteryStats;
+import com.android.server.NetworkManagementService.Dependencies;
+import com.android.server.net.BaseNetworkObserver;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.function.BiFunction;
+
+/**
+ * Tests for {@link NetworkManagementService}.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class NetworkManagementServiceTest {
+ private NetworkManagementService mNMService;
+ @Mock private Context mContext;
+ @Mock private ConnectivityManager mCm;
+ @Mock private IBatteryStats.Stub mBatteryStatsService;
+ @Mock private INetd.Stub mNetdService;
+
+ private static final int TEST_UID = 111;
+
+ @NonNull
+ @Captor
+ private ArgumentCaptor<INetdUnsolicitedEventListener> mUnsolListenerCaptor;
+
+ private final MockDependencies mDeps = new MockDependencies();
+ private final MockPermissionEnforcer mPermissionEnforcer = new MockPermissionEnforcer();
+
+ private final class MockDependencies extends Dependencies {
+ @Override
+ public IBinder getService(String name) {
+ switch (name) {
+ case BatteryStats.SERVICE_NAME:
+ return mBatteryStatsService;
+ default:
+ throw new UnsupportedOperationException("Unknown service " + name);
+ }
+ }
+
+ @Override
+ public void registerLocalService(NetworkManagementInternal nmi) {
+ }
+
+ @Override
+ public INetd getNetd() {
+ return mNetdService;
+ }
+
+ @Override
+ public int getCallingUid() {
+ return Process.SYSTEM_UID;
+ }
+ }
+
+ private static final class MockPermissionEnforcer extends PermissionEnforcer {
+ @Override
+ protected int checkPermission(@NonNull String permission,
+ @NonNull AttributionSource source) {
+ String[] granted = new String [] {
+ android.Manifest.permission.NETWORK_SETTINGS,
+ android.Manifest.permission.OBSERVE_NETWORK_POLICY,
+ android.Manifest.permission.SHUTDOWN
+ };
+ for (String p : granted) {
+ if (p.equals(permission)) {
+ return PermissionCheckerManager.PERMISSION_GRANTED;
+ }
+ }
+ return PermissionCheckerManager.PERMISSION_HARD_DENIED;
+ }
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ doNothing().when(mNetdService)
+ .registerUnsolicitedEventListener(mUnsolListenerCaptor.capture());
+ doReturn(Context.CONNECTIVITY_SERVICE).when(mContext).getSystemServiceName(
+ eq(ConnectivityManager.class));
+ doReturn(mCm).when(mContext).getSystemService(eq(Context.CONNECTIVITY_SERVICE));
+ // The AIDL stub will use PermissionEnforcer to check permission from the caller.
+ // Mock the service. See MockPermissionEnforcer above.
+ doReturn(Context.PERMISSION_ENFORCER_SERVICE).when(mContext).getSystemServiceName(
+ eq(PermissionEnforcer.class));
+ doReturn(mPermissionEnforcer).when(mContext).getSystemService(
+ eq(Context.PERMISSION_ENFORCER_SERVICE));
+
+ // Start the service and wait until it connects to our socket.
+ mNMService = NetworkManagementService.create(mContext, mDeps);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mNMService.shutdown();
+ }
+
+ private static <T> T expectSoon(T mock) {
+ return verify(mock, timeout(200));
+ }
+
+ /**
+ * Tests that network observers work properly.
+ */
+ @Test
+ public void testNetworkObservers() throws Exception {
+ BaseNetworkObserver observer = mock(BaseNetworkObserver.class);
+ doReturn(new Binder()).when(observer).asBinder(); // Used by registerObserver.
+ mNMService.registerObserver(observer);
+
+ // Forget everything that happened to the mock so far, so we can explicitly verify
+ // everything that happens and does not happen to it from now on.
+
+ INetdUnsolicitedEventListener unsolListener = mUnsolListenerCaptor.getValue();
+ reset(observer);
+ // Now call unsolListener methods and ensure that the observer methods are
+ // called. After every method we expect a callback soon after; to ensure that
+ // invalid messages don't cause any callbacks, we call verifyNoMoreInteractions at the end.
+
+ /**
+ * Interface changes.
+ */
+ unsolListener.onInterfaceAdded("rmnet12");
+ expectSoon(observer).interfaceAdded("rmnet12");
+
+ unsolListener.onInterfaceRemoved("eth1");
+ expectSoon(observer).interfaceRemoved("eth1");
+
+ unsolListener.onInterfaceChanged("clat4", true);
+ expectSoon(observer).interfaceStatusChanged("clat4", true);
+
+ unsolListener.onInterfaceLinkStateChanged("rmnet0", false);
+ expectSoon(observer).interfaceLinkStateChanged("rmnet0", false);
+
+ /**
+ * Bandwidth control events.
+ */
+ unsolListener.onQuotaLimitReached("data", "rmnet_usb0");
+ expectSoon(observer).limitReached("data", "rmnet_usb0");
+
+ /**
+ * Interface class activity.
+ */
+ unsolListener.onInterfaceClassActivityChanged(true, 1, 1234, TEST_UID);
+ expectSoon(observer).interfaceClassDataActivityChanged(1, true, 1234, TEST_UID);
+
+ unsolListener.onInterfaceClassActivityChanged(false, 9, 5678, TEST_UID);
+ expectSoon(observer).interfaceClassDataActivityChanged(9, false, 5678, TEST_UID);
+
+ unsolListener.onInterfaceClassActivityChanged(false, 9, 4321, TEST_UID);
+ expectSoon(observer).interfaceClassDataActivityChanged(9, false, 4321, TEST_UID);
+
+ /**
+ * IP address changes.
+ */
+ unsolListener.onInterfaceAddressUpdated("fe80::1/64", "wlan0", 128, 253);
+ expectSoon(observer).addressUpdated("wlan0", new LinkAddress("fe80::1/64", 128, 253));
+
+ unsolListener.onInterfaceAddressRemoved("fe80::1/64", "wlan0", 128, 253);
+ expectSoon(observer).addressRemoved("wlan0", new LinkAddress("fe80::1/64", 128, 253));
+
+ unsolListener.onInterfaceAddressRemoved("2001:db8::1/64", "wlan0", 1, 0);
+ expectSoon(observer).addressRemoved("wlan0", new LinkAddress("2001:db8::1/64", 1, 0));
+
+ /**
+ * DNS information broadcasts.
+ */
+ unsolListener.onInterfaceDnsServerInfo("rmnet_usb0", 3600, new String[]{"2001:db8::1"});
+ expectSoon(observer).interfaceDnsServerInfo("rmnet_usb0", 3600,
+ new String[]{"2001:db8::1"});
+
+ unsolListener.onInterfaceDnsServerInfo("wlan0", 14400,
+ new String[]{"2001:db8::1", "2001:db8::2"});
+ expectSoon(observer).interfaceDnsServerInfo("wlan0", 14400,
+ new String[]{"2001:db8::1", "2001:db8::2"});
+
+ // We don't check for negative lifetimes, only for parse errors.
+ unsolListener.onInterfaceDnsServerInfo("wlan0", -3600, new String[]{"::1"});
+ expectSoon(observer).interfaceDnsServerInfo("wlan0", -3600,
+ new String[]{"::1"});
+
+ // No syntax checking on the addresses.
+ unsolListener.onInterfaceDnsServerInfo("wlan0", 600,
+ new String[]{"", "::", "", "foo", "::1"});
+ expectSoon(observer).interfaceDnsServerInfo("wlan0", 600,
+ new String[]{"", "::", "", "foo", "::1"});
+
+ // Make sure nothing else was called.
+ verifyNoMoreInteractions(observer);
+ }
+
+ @Test
+ public void testFirewallEnabled() {
+ mNMService.setFirewallEnabled(true);
+ assertTrue(mNMService.isFirewallEnabled());
+
+ mNMService.setFirewallEnabled(false);
+ assertFalse(mNMService.isFirewallEnabled());
+ }
+
+ @Test
+ public void testNetworkRestrictedDefault() {
+ assertFalse(mNMService.isNetworkRestricted(TEST_UID));
+ }
+
+ @Test
+ public void testMeteredNetworkRestrictions() throws RemoteException {
+ // Make sure the mocked netd method returns true.
+ doReturn(true).when(mNetdService).bandwidthEnableDataSaver(anyBoolean());
+
+ // Restrict usage of mobile data in background
+ mNMService.setUidOnMeteredNetworkDenylist(TEST_UID, true);
+ assertTrue("Should be true since mobile data usage is restricted",
+ mNMService.isNetworkRestricted(TEST_UID));
+ verify(mCm).addUidToMeteredNetworkDenyList(TEST_UID);
+
+ mNMService.setDataSaverModeEnabled(true);
+ verify(mNetdService).bandwidthEnableDataSaver(true);
+
+ mNMService.setUidOnMeteredNetworkDenylist(TEST_UID, false);
+ assertTrue("Should be true since data saver is on and the uid is not allowlisted",
+ mNMService.isNetworkRestricted(TEST_UID));
+ verify(mCm).removeUidFromMeteredNetworkDenyList(TEST_UID);
+
+ mNMService.setUidOnMeteredNetworkAllowlist(TEST_UID, true);
+ assertFalse("Should be false since data saver is on and the uid is allowlisted",
+ mNMService.isNetworkRestricted(TEST_UID));
+ verify(mCm).addUidToMeteredNetworkAllowList(TEST_UID);
+
+ // remove uid from allowlist and turn datasaver off again
+ mNMService.setUidOnMeteredNetworkAllowlist(TEST_UID, false);
+ verify(mCm).removeUidFromMeteredNetworkAllowList(TEST_UID);
+ mNMService.setDataSaverModeEnabled(false);
+ verify(mNetdService).bandwidthEnableDataSaver(false);
+ assertFalse("Network should not be restricted when data saver is off",
+ mNMService.isNetworkRestricted(TEST_UID));
+ }
+
+ @Test
+ public void testFirewallChains() {
+ final ArrayMap<Integer, ArrayMap<Integer, Boolean>> expected = new ArrayMap<>();
+ // Dozable chain
+ final ArrayMap<Integer, Boolean> isRestrictedForDozable = new ArrayMap<>();
+ isRestrictedForDozable.put(NetworkPolicyManager.FIREWALL_RULE_DEFAULT, true);
+ isRestrictedForDozable.put(INetd.FIREWALL_RULE_ALLOW, false);
+ isRestrictedForDozable.put(INetd.FIREWALL_RULE_DENY, true);
+ expected.put(FIREWALL_CHAIN_DOZABLE, isRestrictedForDozable);
+ // Powersaver chain
+ final ArrayMap<Integer, Boolean> isRestrictedForPowerSave = new ArrayMap<>();
+ isRestrictedForPowerSave.put(NetworkPolicyManager.FIREWALL_RULE_DEFAULT, true);
+ isRestrictedForPowerSave.put(INetd.FIREWALL_RULE_ALLOW, false);
+ isRestrictedForPowerSave.put(INetd.FIREWALL_RULE_DENY, true);
+ expected.put(FIREWALL_CHAIN_POWERSAVE, isRestrictedForPowerSave);
+ // Standby chain
+ final ArrayMap<Integer, Boolean> isRestrictedForStandby = new ArrayMap<>();
+ isRestrictedForStandby.put(NetworkPolicyManager.FIREWALL_RULE_DEFAULT, false);
+ isRestrictedForStandby.put(INetd.FIREWALL_RULE_ALLOW, false);
+ isRestrictedForStandby.put(INetd.FIREWALL_RULE_DENY, true);
+ expected.put(FIREWALL_CHAIN_STANDBY, isRestrictedForStandby);
+ // Restricted mode chain
+ final ArrayMap<Integer, Boolean> isRestrictedForRestrictedMode = new ArrayMap<>();
+ isRestrictedForRestrictedMode.put(NetworkPolicyManager.FIREWALL_RULE_DEFAULT, true);
+ isRestrictedForRestrictedMode.put(INetd.FIREWALL_RULE_ALLOW, false);
+ isRestrictedForRestrictedMode.put(INetd.FIREWALL_RULE_DENY, true);
+ expected.put(FIREWALL_CHAIN_RESTRICTED, isRestrictedForRestrictedMode);
+ // Low Power Standby chain
+ final ArrayMap<Integer, Boolean> isRestrictedForLowPowerStandby = new ArrayMap<>();
+ isRestrictedForLowPowerStandby.put(NetworkPolicyManager.FIREWALL_RULE_DEFAULT, true);
+ isRestrictedForLowPowerStandby.put(INetd.FIREWALL_RULE_ALLOW, false);
+ isRestrictedForLowPowerStandby.put(INetd.FIREWALL_RULE_DENY, true);
+ expected.put(FIREWALL_CHAIN_LOW_POWER_STANDBY, isRestrictedForLowPowerStandby);
+
+ final int[] chains = {
+ FIREWALL_CHAIN_STANDBY,
+ FIREWALL_CHAIN_POWERSAVE,
+ FIREWALL_CHAIN_DOZABLE,
+ FIREWALL_CHAIN_RESTRICTED,
+ FIREWALL_CHAIN_LOW_POWER_STANDBY
+ };
+ final int[] states = {
+ INetd.FIREWALL_RULE_ALLOW,
+ INetd.FIREWALL_RULE_DENY,
+ NetworkPolicyManager.FIREWALL_RULE_DEFAULT
+ };
+ BiFunction<Integer, Integer, String> errorMsg = (chain, state) -> {
+ return String.format("Unexpected value for chain: %s and state: %s",
+ valueToString(INetd.class, "FIREWALL_CHAIN_", chain),
+ valueToString(INetd.class, "FIREWALL_RULE_", state));
+ };
+ for (int chain : chains) {
+ final ArrayMap<Integer, Boolean> expectedValues = expected.get(chain);
+ mNMService.setFirewallChainEnabled(chain, true);
+ verify(mCm).setFirewallChainEnabled(chain, true /* enabled */);
+ for (int state : states) {
+ mNMService.setFirewallUidRule(chain, TEST_UID, state);
+ assertEquals(errorMsg.apply(chain, state),
+ expectedValues.get(state), mNMService.isNetworkRestricted(TEST_UID));
+ }
+ mNMService.setFirewallChainEnabled(chain, false);
+ verify(mCm).setFirewallChainEnabled(chain, false /* enabled */);
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index 537d04fa35e9..d47f063caec5 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -255,7 +255,6 @@ public class UserControllerTest {
.isTrue();
verifyUserAssignedToDisplay(TEST_USER_ID, 42);
- // TODO(b/239982558): might need to change assertions
verify(mInjector.getWindowManager(), never()).startFreezingScreen(anyInt(), anyInt());
verify(mInjector.getWindowManager(), never()).setSwitchingUser(anyBoolean());
verify(mInjector, never()).clearAllLockedTasks(anyString());
@@ -639,6 +638,39 @@ public class UserControllerTest {
/* keyEvictedCallback= */ mKeyEvictedCallback, /* expectLocking= */ true);
}
+ @Test
+ public void testStopUser_invalidUser() {
+ int userId = -1;
+
+ assertThrows(IllegalArgumentException.class,
+ () -> mUserController.stopUser(userId, /* force= */ true,
+ /* allowDelayedLocking= */ true, /* stopUserCallback= */ null,
+ /* keyEvictedCallback= */ null));
+ }
+
+ @Test
+ public void testStopUser_systemUser() {
+ int userId = UserHandle.USER_SYSTEM;
+
+ int r = mUserController.stopUser(userId, /* force= */ true,
+ /* allowDelayedLocking= */ true, /* stopUserCallback= */ null,
+ /* keyEvictedCallback= */ null);
+
+ assertThat(r).isEqualTo(ActivityManager.USER_OP_ERROR_IS_SYSTEM);
+ }
+
+ @Test
+ public void testStopUser_currentUser() {
+ setUpUser(TEST_USER_ID1, /* flags= */ 0);
+ mUserController.startUser(TEST_USER_ID1, /* foreground= */ true);
+
+ int r = mUserController.stopUser(TEST_USER_ID1, /* force= */ true,
+ /* allowDelayedLocking= */ true, /* stopUserCallback= */ null,
+ /* keyEvictedCallback= */ null);
+
+ assertThat(r).isEqualTo(ActivityManager.USER_OP_IS_CURRENT);
+ }
+
/**
* Test conditional delayed locking with mDelayUserDataLocking true.
*/
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index 3e8a07021d6b..d2cf15971335 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -58,15 +58,18 @@ import android.content.ContextWrapper;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
-import android.graphics.Point;
import android.hardware.Sensor;
import android.hardware.display.DisplayManagerInternal;
import android.hardware.input.IInputManager;
+import android.hardware.input.VirtualDpadConfig;
import android.hardware.input.VirtualKeyEvent;
+import android.hardware.input.VirtualKeyboardConfig;
import android.hardware.input.VirtualMouseButtonEvent;
+import android.hardware.input.VirtualMouseConfig;
import android.hardware.input.VirtualMouseRelativeEvent;
import android.hardware.input.VirtualMouseScrollEvent;
import android.hardware.input.VirtualTouchEvent;
+import android.hardware.input.VirtualTouchscreenConfig;
import android.net.MacAddress;
import android.os.Binder;
import android.os.Handler;
@@ -81,6 +84,7 @@ import android.platform.test.annotations.Presubmit;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.util.ArraySet;
+import android.view.Display;
import android.view.DisplayInfo;
import android.view.KeyEvent;
import android.view.WindowManager;
@@ -135,7 +139,37 @@ public class VirtualDeviceManagerServiceTest {
private static final int SENSOR_HANDLE = 64;
private static final Binder BINDER = new Binder("binder");
private static final int FLAG_CANNOT_DISPLAY_ON_REMOTE_DEVICES = 0x00000;
- private static final int VIRTUAL_DEVICE_ID = 42;
+ private static final int VIRTUAL_DEVICE_ID = 42;
+ private static final VirtualDpadConfig DPAD_CONFIG =
+ new VirtualDpadConfig.Builder()
+ .setVendorId(VENDOR_ID)
+ .setProductId(PRODUCT_ID)
+ .setInputDeviceName(DEVICE_NAME)
+ .setAssociatedDisplayId(DISPLAY_ID)
+ .build();
+ private static final VirtualKeyboardConfig KEYBOARD_CONFIG =
+ new VirtualKeyboardConfig.Builder()
+ .setVendorId(VENDOR_ID)
+ .setProductId(PRODUCT_ID)
+ .setInputDeviceName(DEVICE_NAME)
+ .setAssociatedDisplayId(DISPLAY_ID)
+ .build();
+ private static final VirtualMouseConfig MOUSE_CONFIG =
+ new VirtualMouseConfig.Builder()
+ .setVendorId(VENDOR_ID)
+ .setProductId(PRODUCT_ID)
+ .setInputDeviceName(DEVICE_NAME)
+ .setAssociatedDisplayId(DISPLAY_ID)
+ .build();
+ private static final VirtualTouchscreenConfig TOUCHSCREEN_CONFIG =
+ new VirtualTouchscreenConfig.Builder()
+ .setVendorId(VENDOR_ID)
+ .setProductId(PRODUCT_ID)
+ .setInputDeviceName(DEVICE_NAME)
+ .setAssociatedDisplayId(DISPLAY_ID)
+ .setWidthInPixels(WIDTH)
+ .setHeightInPixels(HEIGHT)
+ .build();
private Context mContext;
private InputManagerMockHelper mInputManagerMockHelper;
@@ -212,14 +246,14 @@ public class VirtualDeviceManagerServiceTest {
private ArrayList<ActivityInfo> getActivityInfoList(
String packageName, String name, boolean displayOnRemoveDevices,
- String targetDisplayCategory) {
+ String requiredDisplayCategory) {
ActivityInfo activityInfo = new ActivityInfo();
activityInfo.packageName = packageName;
activityInfo.name = name;
activityInfo.flags = displayOnRemoveDevices
? FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES : FLAG_CANNOT_DISPLAY_ON_REMOTE_DEVICES;
activityInfo.applicationInfo = mApplicationInfoMock;
- activityInfo.targetDisplayCategory = targetDisplayCategory;
+ activityInfo.requiredDisplayCategory = requiredDisplayCategory;
return new ArrayList<>(Arrays.asList(activityInfo));
}
@@ -271,15 +305,50 @@ public class VirtualDeviceManagerServiceTest {
mVdms = new VirtualDeviceManagerService(mContext);
mLocalService = mVdms.getLocalServiceInstance();
- VirtualDeviceParams params = new VirtualDeviceParams
- .Builder()
- .setBlockedActivities(getBlockedActivities())
- .build();
- mDeviceImpl = new VirtualDeviceImpl(mContext,
- mAssociationInfo, new Binder(), /* ownerUid */ 0, VIRTUAL_DEVICE_ID,
- mInputController, mSensorController, (int associationId) -> {},
- mPendingTrampolineCallback, mActivityListener, mRunningAppsChangedCallback, params);
- mVdms.addVirtualDevice(mDeviceImpl);
+ mDeviceImpl = createVirtualDevice(VIRTUAL_DEVICE_ID);
+ }
+
+ @Test
+ public void getDeviceIdForDisplayId_invalidDisplayId_returnsDefault() {
+ VirtualDeviceManagerService.VirtualDeviceManagerImpl vdm =
+ mVdms.new VirtualDeviceManagerImpl();
+
+ assertThat(
+ vdm.getDeviceIdForDisplayId(Display.INVALID_DISPLAY))
+ .isEqualTo(VirtualDeviceManager.DEFAULT_DEVICE_ID);
+ }
+
+ @Test
+ public void getDeviceIdForDisplayId_defaultDisplayId_returnsDefault() {
+ VirtualDeviceManagerService.VirtualDeviceManagerImpl vdm =
+ mVdms.new VirtualDeviceManagerImpl();
+
+ assertThat(
+ vdm.getDeviceIdForDisplayId(Display.DEFAULT_DISPLAY))
+ .isEqualTo(VirtualDeviceManager.DEFAULT_DEVICE_ID);
+ }
+
+ @Test
+ public void getDeviceIdForDisplayId_nonExistentDisplayId_returnsDefault() {
+ VirtualDeviceManagerService.VirtualDeviceManagerImpl vdm =
+ mVdms.new VirtualDeviceManagerImpl();
+ int nonExistentDisplayId = 999;
+
+ assertThat(
+ vdm.getDeviceIdForDisplayId(nonExistentDisplayId))
+ .isEqualTo(VirtualDeviceManager.DEFAULT_DEVICE_ID);
+ }
+
+ @Test
+ public void getDeviceIdForDisplayId_withValidVirtualDisplayId_returnsDeviceId() {
+ VirtualDeviceManagerService.VirtualDeviceManagerImpl vdm =
+ mVdms.new VirtualDeviceManagerImpl();
+ VirtualDeviceImpl virtualDevice = createVirtualDevice(/* virtualDeviceId */ 1000);
+ virtualDevice.mVirtualDisplayIds.add(DISPLAY_ID);
+
+ assertThat(
+ vdm.getDeviceIdForDisplayId(DISPLAY_ID))
+ .isEqualTo(1000);
}
@Test
@@ -477,63 +546,77 @@ public class VirtualDeviceManagerServiceTest {
@Test
public void createVirtualDpad_noDisplay_failsSecurityException() {
- assertThrows(
- SecurityException.class,
- () -> mDeviceImpl.createVirtualDpad(DISPLAY_ID, DEVICE_NAME, VENDOR_ID,
- PRODUCT_ID, BINDER));
+ assertThrows(SecurityException.class,
+ () -> mDeviceImpl.createVirtualDpad(DPAD_CONFIG, BINDER));
}
@Test
public void createVirtualKeyboard_noDisplay_failsSecurityException() {
- assertThrows(
- SecurityException.class,
- () -> mDeviceImpl.createVirtualKeyboard(DISPLAY_ID, DEVICE_NAME, VENDOR_ID,
- PRODUCT_ID, BINDER));
+ assertThrows(SecurityException.class,
+ () -> mDeviceImpl.createVirtualKeyboard(KEYBOARD_CONFIG, BINDER));
}
@Test
public void createVirtualMouse_noDisplay_failsSecurityException() {
- assertThrows(
- SecurityException.class,
- () -> mDeviceImpl.createVirtualMouse(DISPLAY_ID, DEVICE_NAME, VENDOR_ID,
- PRODUCT_ID, BINDER));
+ assertThrows(SecurityException.class,
+ () -> mDeviceImpl.createVirtualMouse(MOUSE_CONFIG, BINDER));
}
@Test
public void createVirtualTouchscreen_noDisplay_failsSecurityException() {
- assertThrows(
- SecurityException.class,
- () -> mDeviceImpl.createVirtualTouchscreen(DISPLAY_ID, DEVICE_NAME,
- VENDOR_ID, PRODUCT_ID, BINDER, new Point(WIDTH, HEIGHT)));
+ assertThrows(SecurityException.class,
+ () -> mDeviceImpl.createVirtualTouchscreen(TOUCHSCREEN_CONFIG, BINDER));
}
@Test
public void createVirtualTouchscreen_zeroDisplayDimension_failsIllegalArgumentException() {
mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
- Point size = new Point(0, 0);
+ final VirtualTouchscreenConfig zeroConfig =
+ new VirtualTouchscreenConfig.Builder()
+ .setVendorId(VENDOR_ID)
+ .setProductId(PRODUCT_ID)
+ .setInputDeviceName(DEVICE_NAME)
+ .setAssociatedDisplayId(DISPLAY_ID)
+ .setWidthInPixels(0)
+ .setHeightInPixels(0)
+ .build();
assertThrows(IllegalArgumentException.class,
- () -> mDeviceImpl.createVirtualTouchscreen(DISPLAY_ID, DEVICE_NAME, VENDOR_ID,
- PRODUCT_ID, BINDER, size));
+ () -> mDeviceImpl.createVirtualTouchscreen(zeroConfig, BINDER));
}
@Test
public void createVirtualTouchscreen_negativeDisplayDimension_failsIllegalArgumentException() {
mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
- Point size = new Point(-100, -100);
+ final VirtualTouchscreenConfig negativeConfig =
+ new VirtualTouchscreenConfig.Builder()
+ .setVendorId(VENDOR_ID)
+ .setProductId(PRODUCT_ID)
+ .setInputDeviceName(DEVICE_NAME)
+ .setAssociatedDisplayId(DISPLAY_ID)
+ .setWidthInPixels(-100)
+ .setHeightInPixels(-100)
+ .build();
assertThrows(IllegalArgumentException.class,
- () -> mDeviceImpl.createVirtualTouchscreen(DISPLAY_ID, DEVICE_NAME, VENDOR_ID,
- PRODUCT_ID, BINDER, size));
+ () -> mDeviceImpl.createVirtualTouchscreen(negativeConfig, BINDER));
+
}
@Test
public void createVirtualTouchscreen_positiveDisplayDimension_successful() {
mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
- Point size = new Point(600, 800);
- mDeviceImpl.createVirtualTouchscreen(DISPLAY_ID, DEVICE_NAME, VENDOR_ID, PRODUCT_ID, BINDER,
- size);
+ VirtualTouchscreenConfig positiveConfig =
+ new VirtualTouchscreenConfig.Builder()
+ .setVendorId(VENDOR_ID)
+ .setProductId(PRODUCT_ID)
+ .setInputDeviceName(DEVICE_NAME)
+ .setAssociatedDisplayId(DISPLAY_ID)
+ .setWidthInPixels(600)
+ .setHeightInPixels(800)
+ .build();
+ mDeviceImpl.createVirtualTouchscreen(positiveConfig, BINDER);
assertWithMessage(
- "Virtual touchscreen should create input device descriptor on successful creation.")
- .that(mInputController.mInputDeviceDescriptors).isNotEmpty();
+ "Virtual touchscreen should create input device descriptor on successful creation"
+ + ".").that(mInputController.getInputDeviceDescriptors()).isNotEmpty();
}
@Test
@@ -548,10 +631,8 @@ public class VirtualDeviceManagerServiceTest {
mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
doCallRealMethod().when(mContext).enforceCallingOrSelfPermission(
eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString());
- assertThrows(
- SecurityException.class,
- () -> mDeviceImpl.createVirtualDpad(DISPLAY_ID, DEVICE_NAME, VENDOR_ID,
- PRODUCT_ID, BINDER));
+ assertThrows(SecurityException.class,
+ () -> mDeviceImpl.createVirtualDpad(DPAD_CONFIG, BINDER));
}
@Test
@@ -559,10 +640,8 @@ public class VirtualDeviceManagerServiceTest {
mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
doCallRealMethod().when(mContext).enforceCallingOrSelfPermission(
eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString());
- assertThrows(
- SecurityException.class,
- () -> mDeviceImpl.createVirtualKeyboard(DISPLAY_ID, DEVICE_NAME, VENDOR_ID,
- PRODUCT_ID, BINDER));
+ assertThrows(SecurityException.class,
+ () -> mDeviceImpl.createVirtualKeyboard(KEYBOARD_CONFIG, BINDER));
}
@Test
@@ -570,10 +649,8 @@ public class VirtualDeviceManagerServiceTest {
mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
doCallRealMethod().when(mContext).enforceCallingOrSelfPermission(
eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString());
- assertThrows(
- SecurityException.class,
- () -> mDeviceImpl.createVirtualMouse(DISPLAY_ID, DEVICE_NAME, VENDOR_ID,
- PRODUCT_ID, BINDER));
+ assertThrows(SecurityException.class,
+ () -> mDeviceImpl.createVirtualMouse(MOUSE_CONFIG, BINDER));
}
@Test
@@ -581,10 +658,8 @@ public class VirtualDeviceManagerServiceTest {
mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
doCallRealMethod().when(mContext).enforceCallingOrSelfPermission(
eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString());
- assertThrows(
- SecurityException.class,
- () -> mDeviceImpl.createVirtualTouchscreen(DISPLAY_ID, DEVICE_NAME,
- VENDOR_ID, PRODUCT_ID, BINDER, new Point(WIDTH, HEIGHT)));
+ assertThrows(SecurityException.class,
+ () -> mDeviceImpl.createVirtualTouchscreen(TOUCHSCREEN_CONFIG, BINDER));
}
@Test
@@ -619,21 +694,19 @@ public class VirtualDeviceManagerServiceTest {
@Test
public void createVirtualDpad_hasDisplay_obtainFileDescriptor() {
mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
- mDeviceImpl.createVirtualDpad(DISPLAY_ID, DEVICE_NAME, VENDOR_ID, PRODUCT_ID,
- BINDER);
- assertWithMessage("Virtual dpad should register fd when the display matches")
- .that(mInputController.mInputDeviceDescriptors).isNotEmpty();
- verify(mNativeWrapperMock).openUinputDpad(eq(DEVICE_NAME), eq(VENDOR_ID),
- eq(PRODUCT_ID), anyString());
+ mDeviceImpl.createVirtualDpad(DPAD_CONFIG, BINDER);
+ assertWithMessage("Virtual dpad should register fd when the display matches").that(
+ mInputController.getInputDeviceDescriptors()).isNotEmpty();
+ verify(mNativeWrapperMock).openUinputDpad(eq(DEVICE_NAME), eq(VENDOR_ID), eq(PRODUCT_ID),
+ anyString());
}
@Test
public void createVirtualKeyboard_hasDisplay_obtainFileDescriptor() {
mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
- mDeviceImpl.createVirtualKeyboard(DISPLAY_ID, DEVICE_NAME, VENDOR_ID, PRODUCT_ID,
- BINDER);
- assertWithMessage("Virtual keyboard should register fd when the display matches")
- .that(mInputController.mInputDeviceDescriptors).isNotEmpty();
+ mDeviceImpl.createVirtualKeyboard(KEYBOARD_CONFIG, BINDER);
+ assertWithMessage("Virtual keyboard should register fd when the display matches").that(
+ mInputController.getInputDeviceDescriptors()).isNotEmpty();
verify(mNativeWrapperMock).openUinputKeyboard(eq(DEVICE_NAME), eq(VENDOR_ID),
eq(PRODUCT_ID), anyString());
}
@@ -641,10 +714,9 @@ public class VirtualDeviceManagerServiceTest {
@Test
public void createVirtualMouse_hasDisplay_obtainFileDescriptor() {
mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
- mDeviceImpl.createVirtualMouse(DISPLAY_ID, DEVICE_NAME, VENDOR_ID, PRODUCT_ID,
- BINDER);
- assertWithMessage("Virtual mouse should register fd when the display matches")
- .that(mInputController.mInputDeviceDescriptors).isNotEmpty();
+ mDeviceImpl.createVirtualMouse(MOUSE_CONFIG, BINDER);
+ assertWithMessage("Virtual mouse should register fd when the display matches").that(
+ mInputController.getInputDeviceDescriptors()).isNotEmpty();
verify(mNativeWrapperMock).openUinputMouse(eq(DEVICE_NAME), eq(VENDOR_ID), eq(PRODUCT_ID),
anyString());
}
@@ -652,10 +724,9 @@ public class VirtualDeviceManagerServiceTest {
@Test
public void createVirtualTouchscreen_hasDisplay_obtainFileDescriptor() {
mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
- mDeviceImpl.createVirtualTouchscreen(DISPLAY_ID, DEVICE_NAME, VENDOR_ID, PRODUCT_ID,
- BINDER, new Point(WIDTH, HEIGHT));
- assertWithMessage("Virtual touchscreen should register fd when the display matches")
- .that(mInputController.mInputDeviceDescriptors).isNotEmpty();
+ mDeviceImpl.createVirtualTouchscreen(TOUCHSCREEN_CONFIG, BINDER);
+ assertWithMessage("Virtual touchscreen should register fd when the display matches").that(
+ mInputController.getInputDeviceDescriptors()).isNotEmpty();
verify(mNativeWrapperMock).openUinputTouchscreen(eq(DEVICE_NAME), eq(VENDOR_ID),
eq(PRODUCT_ID), anyString(), eq(HEIGHT), eq(WIDTH));
}
@@ -909,9 +980,28 @@ public class VirtualDeviceManagerServiceTest {
mDeviceImpl.mVirtualDisplayIds.add(1);
mDeviceImpl.mVirtualDisplayIds.add(2);
mDeviceImpl.mVirtualDisplayIds.add(3);
- mDeviceImpl.createVirtualMouse(1, DEVICE_NAME, VENDOR_ID, PRODUCT_ID, BINDER);
- mDeviceImpl.createVirtualMouse(2, DEVICE_NAME, VENDOR_ID, PRODUCT_ID, BINDER);
- mDeviceImpl.createVirtualMouse(3, DEVICE_NAME, VENDOR_ID, PRODUCT_ID, BINDER);
+ VirtualMouseConfig config1 = new VirtualMouseConfig.Builder()
+ .setAssociatedDisplayId(1)
+ .setInputDeviceName(DEVICE_NAME)
+ .setVendorId(VENDOR_ID)
+ .setProductId(PRODUCT_ID)
+ .build();
+ VirtualMouseConfig config2 = new VirtualMouseConfig.Builder()
+ .setAssociatedDisplayId(2)
+ .setInputDeviceName(DEVICE_NAME)
+ .setVendorId(VENDOR_ID)
+ .setProductId(PRODUCT_ID)
+ .build();
+ VirtualMouseConfig config3 = new VirtualMouseConfig.Builder()
+ .setAssociatedDisplayId(3)
+ .setInputDeviceName(DEVICE_NAME)
+ .setVendorId(VENDOR_ID)
+ .setProductId(PRODUCT_ID)
+ .build();
+
+ mDeviceImpl.createVirtualMouse(config1, BINDER);
+ mDeviceImpl.createVirtualMouse(config2, BINDER);
+ mDeviceImpl.createVirtualMouse(config3, BINDER);
mDeviceImpl.setShowPointerIcon(false);
verify(mInputManagerInternalMock, times(3)).setPointerIconVisible(eq(false), anyInt());
verify(mInputManagerInternalMock, never()).setPointerIconVisible(eq(true), anyInt());
@@ -1107,4 +1197,17 @@ public class VirtualDeviceManagerServiceTest {
intent.filterEquals(blockedAppIntent)), any(), any());
}
+ private VirtualDeviceImpl createVirtualDevice(int virtualDeviceId) {
+ VirtualDeviceParams params = new VirtualDeviceParams
+ .Builder()
+ .setBlockedActivities(getBlockedActivities())
+ .build();
+ VirtualDeviceImpl virtualDeviceImpl = new VirtualDeviceImpl(mContext,
+ mAssociationInfo, new Binder(), /* ownerUid */ 0, virtualDeviceId,
+ mInputController, mSensorController, (int associationId) -> {},
+ mPendingTrampolineCallback, mActivityListener, mRunningAppsChangedCallback, params);
+ mVdms.addVirtualDevice(virtualDeviceImpl);
+ return virtualDeviceImpl;
+ }
+
}
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java
index 3ca648cbcc30..aaa13512af25 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java
@@ -16,7 +16,6 @@
package com.android.server.companion.virtual.audio;
-import static android.companion.AssociationRequest.DEVICE_PROFILE_APP_STREAMING;
import static android.media.AudioAttributes.FLAG_SECURE;
import static android.media.AudioPlaybackConfiguration.PLAYER_STATE_STARTED;
import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
@@ -86,8 +85,9 @@ public class VirtualAudioControllerTest {
/* pipBlockedCallback= */ null,
/* activityBlockedCallback= */ null,
/* secureWindowCallback= */ null,
- /* deviceProfile= */ DEVICE_PROFILE_APP_STREAMING,
- /* displayCategories= */ new ArrayList<>());
+ /* displayCategories= */ new ArrayList<>(),
+ /* recentsPolicy= */
+ VirtualDeviceParams.RECENTS_POLICY_ALLOW_IN_HOST_DEVICE_RECENTS);
}
diff --git a/services/tests/servicestests/src/com/android/server/content/SyncManagerTest.java b/services/tests/servicestests/src/com/android/server/content/SyncManagerTest.java
index c2a81d9453e4..d4e3d4418c43 100644
--- a/services/tests/servicestests/src/com/android/server/content/SyncManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/content/SyncManagerTest.java
@@ -16,12 +16,40 @@
package com.android.server.content;
+import static android.content.pm.UserProperties.INHERIT_DEVICE_POLICY_FROM_PARENT;
+import static android.content.pm.UserProperties.SHOW_IN_LAUNCHER_WITH_PARENT;
+import static android.content.pm.UserProperties.SHOW_IN_SETTINGS_WITH_PARENT;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.accounts.AccountManagerInternal;
import android.content.ContentResolver;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.content.pm.UserProperties;
import android.os.Bundle;
+import android.os.UserManager;
+import android.provider.ContactsContract;
import android.test.suitebuilder.annotation.SmallTest;
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.server.job.JobSchedulerInternal;
+
import junit.framework.TestCase;
+import org.jetbrains.annotations.NotNull;
+import org.junit.Before;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+
/**
* Tests for SyncManager.
*
@@ -33,6 +61,43 @@ public class SyncManagerTest extends TestCase {
final String KEY_1 = "key_1";
final String KEY_2 = "key_2";
+ private SyncManager mSyncManager;
+ private Context mContext;
+
+ @Mock
+ private UserManager mUserManager;
+ @Mock
+ private AccountManagerInternal mAccountManagerInternal;
+ @Mock
+ private JobSchedulerInternal mJobSchedulerInternal;
+
+ private class SyncManagerWithMockedServices extends SyncManager {
+
+ @Override
+ protected AccountManagerInternal getAccountManagerInternal() {
+ return mAccountManagerInternal;
+ }
+
+ @Override
+ protected JobSchedulerInternal getJobSchedulerInternal() {
+ return mJobSchedulerInternal;
+ }
+
+ private SyncManagerWithMockedServices(Context context, boolean factoryTest) {
+ super(context, factoryTest);
+ }
+ }
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mContext = spy(ApplicationProvider.getApplicationContext());
+ when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
+ doNothing().when(mAccountManagerInternal).addOnAppPermissionChangeListener(any());
+ when(mJobSchedulerInternal.getSystemScheduledPendingJobs()).thenReturn(new ArrayList<>());
+ mSyncManager = new SyncManagerWithMockedServices(mContext, true);
+ }
+
public void testSyncExtrasEquals_WithNull() throws Exception {
Bundle b1 = new Bundle();
Bundle b2 = new Bundle();
@@ -140,4 +205,45 @@ public class SyncManagerTest extends TestCase {
final StringBuilder sb = new StringBuilder();
assertEquals(expected, SyncManager.formatDurationHMS(sb, time * 1000).toString());
}
+
+ private UserInfo createUserInfo(String name, int id, int groupId, int flags) {
+ final UserInfo ui = new UserInfo(id, name, flags | UserInfo.FLAG_INITIALIZED);
+ ui.profileGroupId = groupId;
+ return ui;
+ }
+
+ @NotNull
+ private UserProperties getCloneUserProperties() {
+ return new UserProperties.Builder()
+ .setStartWithParent(true)
+ .setShowInLauncher(SHOW_IN_LAUNCHER_WITH_PARENT)
+ .setShowInSettings(SHOW_IN_SETTINGS_WITH_PARENT)
+ .setUseParentsContacts(true)
+ .setInheritDevicePolicy(INHERIT_DEVICE_POLICY_FROM_PARENT)
+ .build();
+ }
+
+ private void mockUserProperties(UserInfo primaryUserInfo, UserInfo cloneUserInfo) {
+ UserProperties cloneUserProperties = getCloneUserProperties();
+ when(mUserManager.getUserProperties(cloneUserInfo.getUserHandle()))
+ .thenReturn(cloneUserProperties);
+ // Set default user properties for primary user
+ when(mUserManager.getUserProperties(primaryUserInfo.getUserHandle()))
+ .thenReturn(new UserProperties.Builder().build());
+ }
+
+ public void testShouldDisableSync() {
+ UserInfo primaryUserInfo = createUserInfo("primary", 0 /* id */, 0 /* groupId */,
+ UserInfo.FLAG_PRIMARY | UserInfo.FLAG_ADMIN);
+ UserInfo cloneUserInfo = createUserInfo("clone", 10 /* id */, 0 /* groupId */,
+ UserInfo.FLAG_PROFILE);
+
+ mockUserProperties(primaryUserInfo, cloneUserInfo);
+
+ // Clone user accounts must have contact syncs disabled
+ assertThat(mSyncManager.shouldDisableSyncForUser(cloneUserInfo,
+ ContactsContract.AUTHORITY)).isTrue();
+ assertThat(mSyncManager.shouldDisableSyncForUser(primaryUserInfo,
+ ContactsContract.AUTHORITY)).isFalse();
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java
index f535fdac6cf6..85a2446cc316 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java
@@ -44,6 +44,7 @@ import org.junit.runner.RunWith;
public class OwnersTest extends DpmTestBase {
private static final String TESTDPC_PACKAGE = "com.afwsamples.testdpc";
+ private final DeviceStateCacheImpl mDeviceStateCache = new DeviceStateCacheImpl();
@Before
public void setUp() throws Exception {
@@ -120,6 +121,6 @@ public class OwnersTest extends DpmTestBase {
final MockSystemServices services = getServices();
return new Owners(services.userManager, services.userManagerInternal,
services.packageManagerInternal, services.activityTaskManagerInternal,
- services.activityManagerInternal, services.pathProvider);
+ services.activityManagerInternal, mDeviceStateCache, services.pathProvider);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
index 020683904397..ae368716526a 100644
--- a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
@@ -18,6 +18,7 @@ package com.android.server.display;
import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED;
+import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.eq;
@@ -176,7 +177,7 @@ public class AutomaticBrightnessControllerTest {
// Send new sensor value and verify
listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux1));
- assertEquals(normalizedBrightness1, mController.getAutomaticScreenBrightness(), 0.001f);
+ assertEquals(normalizedBrightness1, mController.getAutomaticScreenBrightness(), EPSILON);
// Set up system to return 0.0f (minimum possible brightness) as a brightness value
float lux2 = 10.0f;
@@ -190,7 +191,7 @@ public class AutomaticBrightnessControllerTest {
// Send new sensor value and verify
listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux2));
- assertEquals(normalizedBrightness2, mController.getAutomaticScreenBrightness(), 0.001f);
+ assertEquals(normalizedBrightness2, mController.getAutomaticScreenBrightness(), EPSILON);
}
@Test
@@ -219,7 +220,7 @@ public class AutomaticBrightnessControllerTest {
// Send new sensor value and verify
listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux1));
- assertEquals(normalizedBrightness1, mController.getAutomaticScreenBrightness(), 0.001f);
+ assertEquals(normalizedBrightness1, mController.getAutomaticScreenBrightness(), EPSILON);
// Set up system to return 1.0f as a brightness value (brightness_max)
@@ -234,7 +235,7 @@ public class AutomaticBrightnessControllerTest {
// Send new sensor value and verify
listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux2));
- assertEquals(normalizedBrightness2, mController.getAutomaticScreenBrightness(), 0.001f);
+ assertEquals(normalizedBrightness2, mController.getAutomaticScreenBrightness(), EPSILON);
}
@Test
@@ -416,6 +417,12 @@ public class AutomaticBrightnessControllerTest {
// ambient lux goes to 0
listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0));
assertEquals(0.0f, mController.getAmbientLux(), EPSILON);
+
+ // only the values within the horizon should be kept
+ assertArrayEquals(new float[] {10000, 10000, 0, 0, 0}, mController.getLastSensorValues(),
+ EPSILON);
+ assertArrayEquals(new long[] {4000, 4500, 5000, 5500, 6000},
+ mController.getLastSensorTimestamps());
}
@Test
@@ -487,4 +494,92 @@ public class AutomaticBrightnessControllerTest {
0 /* adjustment */, false /* userChanged */, DisplayPowerRequest.POLICY_BRIGHT);
assertEquals(BRIGHTNESS_MAX_FLOAT, mController.getAutomaticScreenBrightness(), 0.0f);
}
+
+ @Test
+ public void testGetSensorReadings() throws Exception {
+ ArgumentCaptor<SensorEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(SensorEventListener.class);
+ verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+ eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+ SensorEventListener listener = listenerCaptor.getValue();
+
+ // Choose values such that the ring buffer's capacity is extended and the buffer is pruned
+ int increment = 11;
+ int lux = 5000;
+ for (int i = 0; i < 1000; i++) {
+ lux += increment;
+ mClock.fastForward(increment);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, lux));
+ }
+
+ int valuesCount = (int) Math.ceil((double) AMBIENT_LIGHT_HORIZON_LONG / increment + 1);
+ float[] sensorValues = mController.getLastSensorValues();
+ long[] sensorTimestamps = mController.getLastSensorTimestamps();
+
+ // Only the values within the horizon should be kept
+ assertEquals(valuesCount, sensorValues.length);
+ assertEquals(valuesCount, sensorTimestamps.length);
+
+ long sensorTimestamp = mClock.now();
+ for (int i = valuesCount - 1; i >= 1; i--) {
+ assertEquals(lux, sensorValues[i], EPSILON);
+ assertEquals(sensorTimestamp, sensorTimestamps[i]);
+ lux -= increment;
+ sensorTimestamp -= increment;
+ }
+ assertEquals(lux, sensorValues[0], EPSILON);
+ assertEquals(mClock.now() - AMBIENT_LIGHT_HORIZON_LONG, sensorTimestamps[0]);
+ }
+
+ @Test
+ public void testGetSensorReadingsFullBuffer() throws Exception {
+ ArgumentCaptor<SensorEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(SensorEventListener.class);
+ verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+ eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+ SensorEventListener listener = listenerCaptor.getValue();
+ int initialCapacity = 150;
+
+ // Choose values such that the ring buffer is pruned
+ int increment1 = 200;
+ int lux = 5000;
+ for (int i = 0; i < 20; i++) {
+ lux += increment1;
+ mClock.fastForward(increment1);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, lux));
+ }
+
+ int valuesCount = (int) Math.ceil((double) AMBIENT_LIGHT_HORIZON_LONG / increment1 + 1);
+
+ // Choose values such that the buffer becomes full
+ int increment2 = 1;
+ for (int i = 0; i < initialCapacity - valuesCount; i++) {
+ lux += increment2;
+ mClock.fastForward(increment2);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, lux));
+ }
+
+ float[] sensorValues = mController.getLastSensorValues();
+ long[] sensorTimestamps = mController.getLastSensorTimestamps();
+
+ // The buffer should be full
+ assertEquals(initialCapacity, sensorValues.length);
+ assertEquals(initialCapacity, sensorTimestamps.length);
+
+ long sensorTimestamp = mClock.now();
+ for (int i = initialCapacity - 1; i >= 1; i--) {
+ assertEquals(lux, sensorValues[i], EPSILON);
+ assertEquals(sensorTimestamp, sensorTimestamps[i]);
+
+ if (i >= valuesCount) {
+ lux -= increment2;
+ sensorTimestamp -= increment2;
+ } else {
+ lux -= increment1;
+ sensorTimestamp -= increment1;
+ }
+ }
+ assertEquals(lux, sensorValues[0], EPSILON);
+ assertEquals(mClock.now() - AMBIENT_LIGHT_HORIZON_LONG, sensorTimestamps[0]);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java b/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java
index c2e8417f2ff0..6def7b1c8c35 100644
--- a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java
@@ -136,28 +136,28 @@ public class BrightnessTrackerTest {
assertNull(mInjector.mSensorListener);
assertNotNull(mInjector.mBroadcastReceiver);
assertTrue(mInjector.mIdleScheduled);
- mInjector.sendScreenChange(/*screen on */ true);
+ mInjector.sendScreenChange(/* screenOn= */ true);
assertNotNull(mInjector.mSensorListener);
assertTrue(mInjector.mColorSamplingEnabled);
- mInjector.sendScreenChange(/*screen on */ false);
+ mInjector.sendScreenChange(/* screenOn= */ false);
assertNull(mInjector.mSensorListener);
assertFalse(mInjector.mColorSamplingEnabled);
// Turn screen on while brightness mode is manual
- mInjector.setBrightnessMode(/* isBrightnessModeAutomatic */ false);
- mInjector.sendScreenChange(/*screen on */ true);
+ mInjector.setBrightnessMode(/* isBrightnessModeAutomatic= */ false);
+ mInjector.sendScreenChange(/* screenOn= */ true);
assertNull(mInjector.mSensorListener);
assertFalse(mInjector.mColorSamplingEnabled);
// Set brightness mode to automatic while screen is off.
- mInjector.sendScreenChange(/*screen on */ false);
- mInjector.setBrightnessMode(/* isBrightnessModeAutomatic */ true);
+ mInjector.sendScreenChange(/* screenOn= */ false);
+ mInjector.setBrightnessMode(/* isBrightnessModeAutomatic= */ true);
assertNull(mInjector.mSensorListener);
assertFalse(mInjector.mColorSamplingEnabled);
// Turn on screen while brightness mode is automatic.
- mInjector.sendScreenChange(/*screen on */ true);
+ mInjector.sendScreenChange(/* screenOn= */ true);
assertNotNull(mInjector.mSensorListener);
assertTrue(mInjector.mColorSamplingEnabled);
@@ -188,14 +188,14 @@ public class BrightnessTrackerTest {
assertFalse(mInjector.mColorSamplingEnabled);
// Pretend screen is off, update config to turn on color sampling.
- mInjector.sendScreenChange(/*screen on */ false);
+ mInjector.sendScreenChange(/* screenOn= */ false);
mTracker.setBrightnessConfiguration(buildBrightnessConfiguration(
/* collectColorSamples= */ true));
mInjector.waitForHandler();
assertFalse(mInjector.mColorSamplingEnabled);
// Pretend screen is on.
- mInjector.sendScreenChange(/*screen on */ true);
+ mInjector.sendScreenChange(/* screenOn= */ true);
assertTrue(mInjector.mColorSamplingEnabled);
mTracker.stop();
@@ -261,7 +261,7 @@ public class BrightnessTrackerTest {
assertFalse(mInjector.mColorSamplingEnabled);
assertNull(mInjector.mDisplayListener);
- mInjector.setBrightnessMode(/*isBrightnessModeAutomatic*/ true);
+ mInjector.setBrightnessMode(/* isBrightnessModeAutomatic= */ true);
assertNotNull(mInjector.mSensorListener);
assertTrue(mInjector.mColorSamplingEnabled);
assertNotNull(mInjector.mDisplayListener);
@@ -272,16 +272,15 @@ public class BrightnessTrackerTest {
mInjector.mColorSamplingEnabled = false;
mInjector.mDisplayListener = null;
// Duplicate notification
- mInjector.setBrightnessMode(/*isBrightnessModeAutomatic*/ true);
+ mInjector.setBrightnessMode(/* isBrightnessModeAutomatic= */ true);
// Sensor shouldn't have been registered as it was already registered.
assertNull(mInjector.mSensorListener);
assertFalse(mInjector.mColorSamplingEnabled);
assertNull(mInjector.mDisplayListener);
- mInjector.mSensorListener = listener;
mInjector.mDisplayListener = displayListener;
mInjector.mColorSamplingEnabled = true;
- mInjector.setBrightnessMode(/*isBrightnessModeAutomatic*/ false);
+ mInjector.setBrightnessMode(/* isBrightnessModeAutomatic= */ false);
assertNull(mInjector.mSensorListener);
assertFalse(mInjector.mColorSamplingEnabled);
assertNull(mInjector.mDisplayListener);
@@ -301,19 +300,21 @@ public class BrightnessTrackerTest {
final String displayId = "1234";
startTracker(mTracker);
- mInjector.mSensorListener.onSensorChanged(createSensorEvent(1.0f));
+ final long sensorTime = TimeUnit.NANOSECONDS.toMillis(mInjector.elapsedRealtimeNanos());
mInjector.incrementTime(TimeUnit.SECONDS.toMillis(2));
- notifyBrightnessChanged(mTracker, brightness, displayId);
+ final long currentTime = mInjector.currentTimeMillis();
+ notifyBrightnessChanged(mTracker, brightness, displayId, new float[] {1.0f},
+ new long[] {sensorTime});
List<BrightnessChangeEvent> events = mTracker.getEvents(0, true).getList();
mTracker.stop();
assertEquals(1, events.size());
BrightnessChangeEvent event = events.get(0);
- assertEquals(mInjector.currentTimeMillis(), event.timeStamp);
+ assertEquals(currentTime, event.timeStamp);
assertEquals(displayId, event.uniqueDisplayId);
assertEquals(1, event.luxValues.length);
assertEquals(1.0f, event.luxValues[0], FLOAT_DELTA);
- assertEquals(mInjector.currentTimeMillis() - TimeUnit.SECONDS.toMillis(2),
+ assertEquals(currentTime - TimeUnit.SECONDS.toMillis(2),
event.luxTimestamps[0]);
assertEquals(brightness, event.brightness, FLOAT_DELTA);
assertEquals(DEFAULT_INITIAL_BRIGHTNESS, event.lastBrightness, FLOAT_DELTA);
@@ -339,9 +340,9 @@ public class BrightnessTrackerTest {
startTracker(mTracker, initialBrightness, DEFAULT_COLOR_SAMPLING_ENABLED);
mInjector.mBroadcastReceiver.onReceive(InstrumentationRegistry.getContext(),
batteryChangeEvent(30, 60));
- mInjector.mSensorListener.onSensorChanged(createSensorEvent(1000.0f));
- final long sensorTime = mInjector.currentTimeMillis();
- notifyBrightnessChanged(mTracker, brightness, displayId);
+ final long currentTime = mInjector.currentTimeMillis();
+ notifyBrightnessChanged(mTracker, brightness, displayId, new float[] {1000.0f},
+ new long[] {TimeUnit.NANOSECONDS.toMillis(mInjector.elapsedRealtimeNanos())});
List<BrightnessChangeEvent> eventsNoPackage
= mTracker.getEvents(0, false).getList();
List<BrightnessChangeEvent> events = mTracker.getEvents(0, true).getList();
@@ -349,10 +350,10 @@ public class BrightnessTrackerTest {
assertEquals(1, events.size());
BrightnessChangeEvent event = events.get(0);
- assertEquals(event.timeStamp, mInjector.currentTimeMillis());
+ assertEquals(event.timeStamp, currentTime);
assertEquals(displayId, event.uniqueDisplayId);
- assertArrayEquals(new float[] {1000.0f}, event.luxValues, 0.01f);
- assertArrayEquals(new long[] {sensorTime}, event.luxTimestamps);
+ assertArrayEquals(new float[] {1000.0f}, event.luxValues, FLOAT_DELTA);
+ assertArrayEquals(new long[] {currentTime}, event.luxTimestamps);
assertEquals(brightness, event.brightness, FLOAT_DELTA);
assertEquals(initialBrightness, event.lastBrightness, FLOAT_DELTA);
assertEquals(0.5, event.batteryLevel, FLOAT_DELTA);
@@ -374,13 +375,12 @@ public class BrightnessTrackerTest {
public void testIgnoreAutomaticBrightnessChange() {
final int initialBrightness = 30;
startTracker(mTracker, initialBrightness, DEFAULT_COLOR_SAMPLING_ENABLED);
- mInjector.mSensorListener.onSensorChanged(createSensorEvent(1.0f));
mInjector.incrementTime(TimeUnit.SECONDS.toMillis(1));
final int systemUpdatedBrightness = 20;
- notifyBrightnessChanged(mTracker, systemUpdatedBrightness, false /*userInitiated*/,
- 0.5f /*powerBrightnessFactor(*/, false /*isUserSetBrightness*/,
- false /*isDefaultBrightnessConfig*/, DEFAULT_DISPLAY_ID);
+ notifyBrightnessChanged(mTracker, systemUpdatedBrightness, /* userInitiated= */ false,
+ /* powerBrightnessFactor= */ 0.5f, /* isUserSetBrightness= */ false,
+ /* isDefaultBrightnessConfig= */ false, DEFAULT_DISPLAY_ID);
List<BrightnessChangeEvent> events = mTracker.getEvents(0, true).getList();
// No events because we filtered out our change.
assertEquals(0, events.size());
@@ -408,10 +408,8 @@ public class BrightnessTrackerTest {
@Test
public void testLimitedBufferSize() {
startTracker(mTracker);
- mInjector.mSensorListener.onSensorChanged(createSensorEvent(1.0f));
for (int brightness = 0; brightness <= 255; ++brightness) {
- mInjector.mSensorListener.onSensorChanged(createSensorEvent(1.0f));
mInjector.incrementTime(TimeUnit.SECONDS.toNanos(1));
notifyBrightnessChanged(mTracker, brightness);
}
@@ -427,33 +425,6 @@ public class BrightnessTrackerTest {
}
@Test
- public void testLimitedSensorEvents() {
- final int brightness = 20;
-
- startTracker(mTracker);
- // 20 Sensor events 1 second apart.
- for (int i = 0; i < 20; ++i) {
- mInjector.incrementTime(TimeUnit.SECONDS.toMillis(1));
- mInjector.mSensorListener.onSensorChanged(createSensorEvent(i + 1.0f));
- }
- notifyBrightnessChanged(mTracker, 20);
- List<BrightnessChangeEvent> events = mTracker.getEvents(0, true).getList();
- mTracker.stop();
-
- assertEquals(1, events.size());
- BrightnessChangeEvent event = events.get(0);
- assertEquals(mInjector.currentTimeMillis(), event.timeStamp);
-
- // 12 sensor events, 11 for 0->10 seconds + 1 previous event.
- assertEquals(12, event.luxValues.length);
- for (int i = 0; i < 12; ++i) {
- assertEquals(event.luxTimestamps[11 - i],
- mInjector.currentTimeMillis() - i * TimeUnit.SECONDS.toMillis(1));
- }
- assertEquals(brightness, event.brightness, FLOAT_DELTA);
- }
-
- @Test
public void testReadEvents() throws Exception {
BrightnessTracker tracker = new BrightnessTracker(InstrumentationRegistry.getContext(),
mInjector);
@@ -607,15 +578,16 @@ public class BrightnessTrackerTest {
startTracker(mTracker);
mInjector.mBroadcastReceiver.onReceive(InstrumentationRegistry.getContext(),
batteryChangeEvent(30, 100));
- mInjector.mSensorListener.onSensorChanged(createSensorEvent(2000.0f));
- final long firstSensorTime = mInjector.currentTimeMillis();
+ final long elapsedTime1 = TimeUnit.NANOSECONDS.toMillis(mInjector.elapsedRealtimeNanos());
+ final long currentTime1 = mInjector.currentTimeMillis();
mInjector.incrementTime(TimeUnit.SECONDS.toMillis(2));
- mInjector.mSensorListener.onSensorChanged(createSensorEvent(3000.0f));
- final long secondSensorTime = mInjector.currentTimeMillis();
+ final long elapsedTime2 = TimeUnit.NANOSECONDS.toMillis(mInjector.elapsedRealtimeNanos());
+ final long currentTime2 = mInjector.currentTimeMillis();
mInjector.incrementTime(TimeUnit.SECONDS.toMillis(3));
- notifyBrightnessChanged(mTracker, brightness, true /*userInitiated*/,
- 0.5f /*powerBrightnessFactor*/, true /*hasUserBrightnessPoints*/,
- false /*isDefaultBrightnessConfig*/, displayId);
+ notifyBrightnessChanged(mTracker, brightness, /* userInitiated= */ true,
+ /* powerBrightnessFactor= */ 0.5f, /* isUserSetBrightness= */ true,
+ /* isDefaultBrightnessConfig= */ false, displayId, new float[] {2000.0f, 3000.0f},
+ new long[] {elapsedTime1, elapsedTime2});
ByteArrayOutputStream baos = new ByteArrayOutputStream();
mTracker.writeEventsLocked(baos);
mTracker.stop();
@@ -631,7 +603,7 @@ public class BrightnessTrackerTest {
BrightnessChangeEvent event = events.get(0);
assertEquals(displayId, event.uniqueDisplayId);
assertArrayEquals(new float[] {2000.0f, 3000.0f}, event.luxValues, FLOAT_DELTA);
- assertArrayEquals(new long[] {firstSensorTime, secondSensorTime}, event.luxTimestamps);
+ assertArrayEquals(new long[] {currentTime1, currentTime2}, event.luxTimestamps);
assertEquals(brightness, event.brightness, FLOAT_DELTA);
assertEquals(0.3, event.batteryLevel, FLOAT_DELTA);
assertTrue(event.nightMode);
@@ -647,53 +619,6 @@ public class BrightnessTrackerTest {
}
@Test
- public void testWritePrunesOldEvents() throws Exception {
- final int brightness = 20;
-
- mInjector.mSecureIntSettings.put(Settings.Secure.NIGHT_DISPLAY_ACTIVATED, 1);
- mInjector.mSecureIntSettings.put(Settings.Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE, 3339);
-
- mInjector.mSecureIntSettings.put(Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED, 1);
- mInjector.mSecureIntSettings.put(Settings.Secure.REDUCE_BRIGHT_COLORS_LEVEL, 40);
-
- startTracker(mTracker);
- mInjector.mBroadcastReceiver.onReceive(InstrumentationRegistry.getContext(),
- batteryChangeEvent(30, 100));
- mInjector.mSensorListener.onSensorChanged(createSensorEvent(1000.0f));
- mInjector.incrementTime(TimeUnit.SECONDS.toMillis(1));
- mInjector.mSensorListener.onSensorChanged(createSensorEvent(2000.0f));
- final long sensorTime = mInjector.currentTimeMillis();
- notifyBrightnessChanged(mTracker, brightness);
-
- // 31 days later
- mInjector.incrementTime(TimeUnit.DAYS.toMillis(31));
- mInjector.mSensorListener.onSensorChanged(createSensorEvent(3000.0f));
- notifyBrightnessChanged(mTracker, brightness);
- final long eventTime = mInjector.currentTimeMillis();
-
- List<BrightnessChangeEvent> events = mTracker.getEvents(0, true).getList();
- assertEquals(2, events.size());
-
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- mTracker.writeEventsLocked(baos);
- events = mTracker.getEvents(0, true).getList();
- mTracker.stop();
-
- assertEquals(1, events.size());
- BrightnessChangeEvent event = events.get(0);
- assertEquals(eventTime, event.timeStamp);
-
- // We will keep one of the old sensor events because we keep 1 event outside the window.
- assertArrayEquals(new float[] {2000.0f, 3000.0f}, event.luxValues, FLOAT_DELTA);
- assertArrayEquals(new long[] {sensorTime, eventTime}, event.luxTimestamps);
- assertEquals(brightness, event.brightness, FLOAT_DELTA);
- assertEquals(0.3, event.batteryLevel, FLOAT_DELTA);
- assertTrue(event.nightMode);
- assertTrue(event.reduceBrightColors);
- assertEquals(3339, event.colorTemperature);
- }
-
- @Test
public void testParcelUnParcel() {
Parcel parcel = Parcel.obtain();
BrightnessChangeEvent.Builder builder = new BrightnessChangeEvent.Builder();
@@ -796,9 +721,10 @@ public class BrightnessTrackerTest {
// Send an event.
long eventTime = mInjector.currentTimeMillis();
- mTracker.notifyBrightnessChanged(brightness, true /*userInitiated*/,
- 1.0f /*powerBrightnessFactor*/, false /*isUserSetBrightness*/,
- false /*isDefaultBrightnessConfig*/, DEFAULT_DISPLAY_ID);
+ mTracker.notifyBrightnessChanged(brightness, /* userInitiated= */ true,
+ /* powerBrightnessFactor= */ 1.0f, /* isUserSetBrightness= */ false,
+ /* isDefaultBrightnessConfig= */ false, DEFAULT_DISPLAY_ID, new float[10],
+ new long[10]);
// Time passes before handler can run.
mInjector.incrementTime(TimeUnit.SECONDS.toMillis(2));
@@ -890,20 +816,33 @@ public class BrightnessTrackerTest {
public void testOnlyOneReceiverRegistered() {
assertNull(mInjector.mLightSensor);
assertNull(mInjector.mSensorListener);
+ assertNull(mInjector.mContentObserver);
+ assertNull(mInjector.mBroadcastReceiver);
+ assertFalse(mInjector.mIdleScheduled);
startTracker(mTracker, 0.3f, false);
assertNotNull(mInjector.mLightSensor);
assertNotNull(mInjector.mSensorListener);
+ assertNotNull(mInjector.mContentObserver);
+ assertNotNull(mInjector.mBroadcastReceiver);
+ assertTrue(mInjector.mIdleScheduled);
Sensor registeredLightSensor = mInjector.mLightSensor;
SensorEventListener registeredSensorListener = mInjector.mSensorListener;
+ ContentObserver registeredContentObserver = mInjector.mContentObserver;
+ BroadcastReceiver registeredBroadcastReceiver = mInjector.mBroadcastReceiver;
mTracker.start(0.3f);
assertSame(registeredLightSensor, mInjector.mLightSensor);
assertSame(registeredSensorListener, mInjector.mSensorListener);
+ assertSame(registeredContentObserver, mInjector.mContentObserver);
+ assertSame(registeredBroadcastReceiver, mInjector.mBroadcastReceiver);
mTracker.stop();
assertNull(mInjector.mLightSensor);
assertNull(mInjector.mSensorListener);
+ assertNull(mInjector.mContentObserver);
+ assertNull(mInjector.mBroadcastReceiver);
+ assertFalse(mInjector.mIdleScheduled);
// mInjector asserts that we aren't removing a null receiver
mTracker.stop();
@@ -954,23 +893,41 @@ public class BrightnessTrackerTest {
private void notifyBrightnessChanged(BrightnessTracker tracker, float brightness,
String displayId) {
- notifyBrightnessChanged(tracker, brightness, true /*userInitiated*/,
- 1.0f /*powerBrightnessFactor*/, false /*isUserSetBrightness*/,
- false /*isDefaultBrightnessConfig*/, displayId);
+ notifyBrightnessChanged(tracker, brightness, /* userInitiated= */ true,
+ /* powerBrightnessFactor= */ 1.0f, /* isUserSetBrightness= */ false,
+ /* isDefaultBrightnessConfig= */ false, displayId, new float[10], new long[10]);
+ }
+
+ private void notifyBrightnessChanged(BrightnessTracker tracker, float brightness,
+ String displayId, float[] luxValues, long[] luxTimestamps) {
+ notifyBrightnessChanged(tracker, brightness, /* userInitiated= */ true,
+ /* powerBrightnessFactor= */ 1.0f, /* isUserSetBrightness= */ false,
+ /* isDefaultBrightnessConfig= */ false, displayId, luxValues, luxTimestamps);
}
private void notifyBrightnessChanged(BrightnessTracker tracker, float brightness,
boolean userInitiated, float powerBrightnessFactor, boolean isUserSetBrightness,
boolean isDefaultBrightnessConfig, String displayId) {
tracker.notifyBrightnessChanged(brightness, userInitiated, powerBrightnessFactor,
- isUserSetBrightness, isDefaultBrightnessConfig, displayId);
+ isUserSetBrightness, isDefaultBrightnessConfig, displayId, new float[10],
+ new long[10]);
+ mInjector.waitForHandler();
+ }
+
+ private void notifyBrightnessChanged(BrightnessTracker tracker, float brightness,
+ boolean userInitiated, float powerBrightnessFactor, boolean isUserSetBrightness,
+ boolean isDefaultBrightnessConfig, String displayId, float[] luxValues,
+ long[] luxTimestamps) {
+ tracker.notifyBrightnessChanged(brightness, userInitiated, powerBrightnessFactor,
+ isUserSetBrightness, isDefaultBrightnessConfig, displayId, luxValues,
+ luxTimestamps);
mInjector.waitForHandler();
}
private BrightnessConfiguration buildBrightnessConfiguration(boolean collectColorSamples) {
BrightnessConfiguration.Builder builder = new BrightnessConfiguration.Builder(
- /* lux = */ new float[] {0f, 10f, 100f},
- /* nits = */ new float[] {1f, 90f, 100f});
+ /* lux= */ new float[] {0f, 10f, 100f},
+ /* nits= */ new float[] {1f, 90f, 100f});
builder.setShouldCollectColorSamples(collectColorSamples);
return builder.build();
}
diff --git a/services/tests/servicestests/src/com/android/server/display/DeviceStateToLayoutMapTest.java b/services/tests/servicestests/src/com/android/server/display/DeviceStateToLayoutMapTest.java
new file mode 100644
index 000000000000..bcae50e1e53b
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/display/DeviceStateToLayoutMapTest.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+
+import static org.junit.Assert.assertEquals;
+
+import android.view.DisplayAddress;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.display.layout.DisplayIdProducer;
+import com.android.server.display.layout.Layout;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+
+@SmallTest
+public class DeviceStateToLayoutMapTest {
+ private DeviceStateToLayoutMap mDeviceStateToLayoutMap;
+
+ @Mock DisplayIdProducer mDisplayIdProducerMock;
+
+ @Before
+ public void setUp() throws IOException {
+ MockitoAnnotations.initMocks(this);
+
+ Mockito.when(mDisplayIdProducerMock.getId(false)).thenReturn(1);
+
+ setupDeviceStateToLayoutMap();
+ }
+
+ //////////////////
+ // Test Methods //
+ //////////////////
+
+ @Test
+ public void testInitialState() {
+ Layout configLayout = mDeviceStateToLayoutMap.get(0);
+
+ Layout testLayout = new Layout();
+ testLayout.createDisplayLocked(
+ DisplayAddress.fromPhysicalDisplayId(123456L), /* isDefault= */ true,
+ /* isEnabled= */ true, mDisplayIdProducerMock);
+ testLayout.createDisplayLocked(
+ DisplayAddress.fromPhysicalDisplayId(78910L), /* isDefault= */ false,
+ /* isEnabled= */ false, mDisplayIdProducerMock);
+ assertEquals(testLayout, configLayout);
+ }
+
+ @Test
+ public void testSwitchedState() {
+ Layout configLayout = mDeviceStateToLayoutMap.get(1);
+
+ Layout testLayout = new Layout();
+ testLayout.createDisplayLocked(
+ DisplayAddress.fromPhysicalDisplayId(78910L), /* isDefault= */ true,
+ /* isEnabled= */ true, mDisplayIdProducerMock);
+ testLayout.createDisplayLocked(
+ DisplayAddress.fromPhysicalDisplayId(123456L), /* isDefault= */ false,
+ /* isEnabled= */ false, mDisplayIdProducerMock);
+
+ assertEquals(testLayout, configLayout);
+ }
+
+ @Test
+ public void testConcurrentState() {
+ Layout configLayout = mDeviceStateToLayoutMap.get(2);
+
+ Layout testLayout = new Layout();
+ testLayout.createDisplayLocked(
+ DisplayAddress.fromPhysicalDisplayId(345L), /* isDefault= */ true,
+ /* isEnabled= */ true, mDisplayIdProducerMock);
+ testLayout.createDisplayLocked(
+ DisplayAddress.fromPhysicalDisplayId(678L), /* isDefault= */ false,
+ /* isEnabled= */ true, mDisplayIdProducerMock);
+
+ assertEquals(testLayout, configLayout);
+ }
+
+ ////////////////////
+ // Helper Methods //
+ ////////////////////
+
+ private void setupDeviceStateToLayoutMap() throws IOException {
+ Path tempFile = Files.createTempFile("device_state_layout_map", ".tmp");
+ Files.write(tempFile, getContent().getBytes(StandardCharsets.UTF_8));
+ mDeviceStateToLayoutMap = new DeviceStateToLayoutMap(mDisplayIdProducerMock,
+ tempFile.toFile());
+ }
+
+ private String getContent() {
+ return "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+ + "<layouts>\n"
+ + "<layout>\n"
+ + "<state>0</state> \n"
+ + "<display enabled=\"true\" defaultDisplay=\"true\">\n"
+ + "<address>123456</address>\n"
+ + "</display>\n"
+ + "<display enabled=\"false\">\n"
+ + "<address>78910</address>\n"
+ + "</display>\n"
+ + "</layout>\n"
+
+ + "<layout>\n"
+ + "<state>1</state> \n"
+ + "<display enabled=\"true\" defaultDisplay=\"true\">\n"
+ + "<address>78910</address>\n"
+ + "</display>\n"
+ + "<display enabled=\"false\">\n"
+ + "<address>123456</address>\n"
+ + "</display>\n"
+ + "</layout>\n"
+
+ + "<layout>\n"
+ + "<state>2</state> \n"
+ + "<display enabled=\"true\" defaultDisplay=\"true\">\n"
+ + "<address>345</address>\n"
+ + "</display>\n"
+ + "<display enabled=\"true\">\n"
+ + "<address>678</address>\n"
+ + "</display>\n"
+ + "</layout>\n"
+ + "</layouts>\n";
+ }
+}
+
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
index 109abd03747c..f676a3f84c0f 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -16,7 +16,9 @@
package com.android.server.display;
+import static android.Manifest.permission.ADD_ALWAYS_UNLOCKED_DISPLAY;
import static android.Manifest.permission.ADD_TRUSTED_DISPLAY;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP;
@@ -212,15 +214,16 @@ public class DisplayManagerServiceTest {
.thenReturn(mMockDisplayToken);
SurfaceControl.StaticDisplayInfo staticDisplayInfo = new SurfaceControl.StaticDisplayInfo();
staticDisplayInfo.isInternal = true;
- when(mSurfaceControlProxy.getStaticDisplayInfo(mMockDisplayToken))
+ when(mSurfaceControlProxy.getStaticDisplayInfo(anyLong()))
.thenReturn(staticDisplayInfo);
SurfaceControl.DynamicDisplayInfo dynamicDisplayMode =
new SurfaceControl.DynamicDisplayInfo();
SurfaceControl.DisplayMode displayMode = new SurfaceControl.DisplayMode();
displayMode.width = 100;
displayMode.height = 200;
+ displayMode.supportedHdrTypes = new int[]{1, 2};
dynamicDisplayMode.supportedDisplayModes = new SurfaceControl.DisplayMode[] {displayMode};
- when(mSurfaceControlProxy.getDynamicDisplayInfo(mMockDisplayToken))
+ when(mSurfaceControlProxy.getDynamicDisplayInfo(anyLong()))
.thenReturn(dynamicDisplayMode);
when(mSurfaceControlProxy.getDesiredDisplayModeSpecs(mMockDisplayToken))
.thenReturn(new SurfaceControl.DesiredDisplayModeSpecs());
@@ -723,9 +726,6 @@ public class DisplayManagerServiceTest {
registerDefaultDisplays(displayManager);
when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
- when(mContext.checkCallingPermission(ADD_TRUSTED_DISPLAY))
- .thenReturn(PackageManager.PERMISSION_DENIED);
-
IVirtualDevice virtualDevice = mock(IVirtualDevice.class);
when(mMockVirtualDeviceManagerInternal.isValidVirtualDevice(virtualDevice))
.thenReturn(true);
@@ -779,9 +779,6 @@ public class DisplayManagerServiceTest {
registerDefaultDisplays(displayManager);
when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
- when(mContext.checkCallingPermission(ADD_TRUSTED_DISPLAY))
- .thenReturn(PackageManager.PERMISSION_DENIED);
-
IVirtualDevice virtualDevice = mock(IVirtualDevice.class);
when(mMockVirtualDeviceManagerInternal.isValidVirtualDevice(virtualDevice))
.thenReturn(true);
@@ -791,7 +788,7 @@ public class DisplayManagerServiceTest {
// virtual device.
final VirtualDisplayConfig.Builder builder1 =
new VirtualDisplayConfig.Builder(VIRTUAL_DISPLAY_NAME, 600, 800, 320)
- .setUniqueId("uniqueId --- device display group 1");
+ .setUniqueId("uniqueId --- device display group");
int displayId1 =
localService.createVirtualDisplay(
@@ -807,7 +804,7 @@ public class DisplayManagerServiceTest {
final VirtualDisplayConfig.Builder builder2 =
new VirtualDisplayConfig.Builder(VIRTUAL_DISPLAY_NAME, 600, 800, 320)
.setFlags(VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP)
- .setUniqueId("uniqueId --- device display group 1");
+ .setUniqueId("uniqueId --- own display group");
int displayId2 =
localService.createVirtualDisplay(
@@ -826,6 +823,99 @@ public class DisplayManagerServiceTest {
}
@Test
+ public void displaysInDeviceOrOwnDisplayGroupShouldPreserveAlwaysUnlockedFlag()
+ throws Exception {
+ DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+ DisplayManagerInternal localService = displayManager.new LocalService();
+
+ registerDefaultDisplays(displayManager);
+ when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+
+ IVirtualDevice virtualDevice = mock(IVirtualDevice.class);
+ when(mMockVirtualDeviceManagerInternal.isValidVirtualDevice(virtualDevice))
+ .thenReturn(true);
+ when(virtualDevice.getDeviceId()).thenReturn(1);
+
+ // Allow an ALWAYS_UNLOCKED display to be created.
+ when(mContext.checkCallingPermission(ADD_TRUSTED_DISPLAY))
+ .thenReturn(PackageManager.PERMISSION_GRANTED);
+
+ when(mContext.checkCallingPermission(ADD_ALWAYS_UNLOCKED_DISPLAY))
+ .thenReturn(PackageManager.PERMISSION_GRANTED);
+
+ // Create a virtual display in a device display group.
+ final VirtualDisplayConfig deviceDisplayGroupDisplayConfig =
+ new VirtualDisplayConfig.Builder(VIRTUAL_DISPLAY_NAME, 600, 800, 320)
+ .setUniqueId("uniqueId --- device display group 1")
+ .setFlags(VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED)
+ .build();
+
+ int deviceDisplayGroupDisplayId =
+ localService.createVirtualDisplay(
+ deviceDisplayGroupDisplayConfig,
+ mMockAppToken /* callback */,
+ virtualDevice /* virtualDeviceToken */,
+ mock(DisplayWindowPolicyController.class),
+ PACKAGE_NAME);
+
+ // Check that FLAG_ALWAYS_UNLOCKED is set.
+ assertNotEquals(
+ "FLAG_ALWAYS_UNLOCKED should be set for displays created in a device display"
+ + " group.",
+ (displayManager.getDisplayDeviceInfoInternal(deviceDisplayGroupDisplayId).flags
+ & DisplayDeviceInfo.FLAG_ALWAYS_UNLOCKED),
+ 0);
+
+ // Create a virtual display in its own display group.
+ final VirtualDisplayConfig ownDisplayGroupConfig =
+ new VirtualDisplayConfig.Builder(VIRTUAL_DISPLAY_NAME, 600, 800, 320)
+ .setUniqueId("uniqueId --- own display group 1")
+ .setFlags(
+ VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED
+ | VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP)
+ .build();
+
+ int ownDisplayGroupDisplayId =
+ localService.createVirtualDisplay(
+ ownDisplayGroupConfig,
+ mMockAppToken /* callback */,
+ virtualDevice /* virtualDeviceToken */,
+ mock(DisplayWindowPolicyController.class),
+ PACKAGE_NAME);
+
+ // Check that FLAG_ALWAYS_UNLOCKED is set.
+ assertNotEquals(
+ "FLAG_ALWAYS_UNLOCKED should be set for displays created in their own display"
+ + " group.",
+ (displayManager.getDisplayDeviceInfoInternal(ownDisplayGroupDisplayId).flags
+ & DisplayDeviceInfo.FLAG_ALWAYS_UNLOCKED),
+ 0);
+
+ // Create a virtual display in a device display group.
+ final VirtualDisplayConfig defaultDisplayGroupConfig =
+ new VirtualDisplayConfig.Builder(VIRTUAL_DISPLAY_NAME, 600, 800, 320)
+ .setUniqueId("uniqueId --- default display group 1")
+ .setFlags(VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED)
+ .build();
+
+ int defaultDisplayGroupDisplayId =
+ localService.createVirtualDisplay(
+ defaultDisplayGroupConfig,
+ mMockAppToken /* callback */,
+ null /* virtualDeviceToken */,
+ mock(DisplayWindowPolicyController.class),
+ PACKAGE_NAME);
+
+ // Check that FLAG_ALWAYS_UNLOCKED is not set.
+ assertEquals(
+ "FLAG_ALWAYS_UNLOCKED should not be set for displays created in the default"
+ + " display group.",
+ (displayManager.getDisplayDeviceInfoInternal(defaultDisplayGroupDisplayId).flags
+ & DisplayDeviceInfo.FLAG_ALWAYS_UNLOCKED),
+ 0);
+ }
+
+ @Test
public void testGetDisplayIdToMirror() throws Exception {
DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
registerDefaultDisplays(displayManager);
@@ -1163,6 +1253,76 @@ public class DisplayManagerServiceTest {
}
/**
+ * Tests that there is a display change notification if the render frame rate is updated
+ */
+ @Test
+ public void testShouldNotifyChangeWhenDisplayInfoRenderFrameRateChanged() throws Exception {
+ DisplayManagerService displayManager =
+ new DisplayManagerService(mContext, mShortMockedInjector);
+ DisplayManagerService.BinderService displayManagerBinderService =
+ displayManager.new BinderService();
+ registerDefaultDisplays(displayManager);
+ displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
+
+ FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager, new float[]{60f});
+ FakeDisplayManagerCallback callback = registerDisplayListenerCallback(displayManager,
+ displayManagerBinderService, displayDevice);
+
+ updateRenderFrameRate(displayManager, displayDevice, 30f);
+ assertTrue(callback.mDisplayChangedCalled);
+ callback.clear();
+
+ updateRenderFrameRate(displayManager, displayDevice, 30f);
+ assertFalse(callback.mDisplayChangedCalled);
+
+ updateRenderFrameRate(displayManager, displayDevice, 20f);
+ assertTrue(callback.mDisplayChangedCalled);
+ callback.clear();
+ }
+
+ /**
+ * Tests that the DisplayInfo is updated correctly with a render frame rate
+ */
+ @Test
+ public void testDisplayInfoRenderFrameRate() throws Exception {
+ DisplayManagerService displayManager =
+ new DisplayManagerService(mContext, mShortMockedInjector);
+ DisplayManagerService.BinderService displayManagerBinderService =
+ displayManager.new BinderService();
+ registerDefaultDisplays(displayManager);
+ displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
+
+ FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager,
+ new float[]{60f, 30f, 20f});
+ int displayId = getDisplayIdForDisplayDevice(displayManager, displayManagerBinderService,
+ displayDevice);
+ DisplayInfo displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
+ assertEquals(60f, displayInfo.getRefreshRate(), 0.01f);
+
+ updateRenderFrameRate(displayManager, displayDevice, 20f);
+ displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
+ assertEquals(20f, displayInfo.getRefreshRate(), 0.01f);
+ }
+
+ /**
+ * Tests that the mode reflects the render frame rate is in compat mode
+ */
+ @Test
+ @DisableCompatChanges({DisplayManagerService.DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE})
+ public void testDisplayInfoRenderFrameRateModeCompat() throws Exception {
+ testDisplayInfoRenderFrameRateModeCompat(/*compatChangeEnabled*/ false);
+ }
+
+ /**
+ * Tests that the mode reflects the physical display refresh rate when not in compat mode.
+ */
+ @Test
+ @EnableCompatChanges({DisplayManagerService.DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE})
+ public void testDisplayInfoRenderFrameRateMode() throws Exception {
+ testDisplayInfoRenderFrameRateModeCompat(/*compatChangeEnabled*/ true);
+ }
+
+ /**
* Tests that EVENT_DISPLAY_ADDED is sent when a display is added.
*/
@Test
@@ -1382,6 +1542,34 @@ public class DisplayManagerServiceTest {
assertEquals(expectedMode, displayInfo.getMode());
}
+ private void testDisplayInfoRenderFrameRateModeCompat(boolean compatChangeEnabled)
+ throws Exception {
+ DisplayManagerService displayManager =
+ new DisplayManagerService(mContext, mShortMockedInjector);
+ DisplayManagerService.BinderService displayManagerBinderService =
+ displayManager.new BinderService();
+ registerDefaultDisplays(displayManager);
+ displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
+
+ FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager,
+ new float[]{60f, 30f, 20f});
+ int displayId = getDisplayIdForDisplayDevice(displayManager, displayManagerBinderService,
+ displayDevice);
+ DisplayInfo displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
+ assertEquals(60f, displayInfo.getRefreshRate(), 0.01f);
+
+ updateRenderFrameRate(displayManager, displayDevice, 20f);
+ displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
+ assertEquals(20f, displayInfo.getRefreshRate(), 0.01f);
+ Display.Mode expectedMode;
+ if (compatChangeEnabled) {
+ expectedMode = new Display.Mode(1, 100, 200, 60f);
+ } else {
+ expectedMode = new Display.Mode(3, 100, 200, 20f);
+ }
+ assertEquals(expectedMode, displayInfo.getMode());
+ }
+
private void testDisplayInfoNonNativeFrameRateOverrideMode(boolean compatChangeEnabled) {
DisplayManagerService displayManager =
new DisplayManagerService(mContext, mBasicInjector);
@@ -1451,6 +1639,15 @@ public class DisplayManagerServiceTest {
updateDisplayDeviceInfo(displayManager, displayDevice, displayDeviceInfo);
}
+ private void updateRenderFrameRate(DisplayManagerService displayManager,
+ FakeDisplayDevice displayDevice,
+ float renderFrameRate) {
+ DisplayDeviceInfo displayDeviceInfo = new DisplayDeviceInfo();
+ displayDeviceInfo.copyFrom(displayDevice.getDisplayDeviceInfoLocked());
+ displayDeviceInfo.renderFrameRate = renderFrameRate;
+ updateDisplayDeviceInfo(displayManager, displayDevice, displayDeviceInfo);
+ }
+
private void updateModeId(DisplayManagerService displayManager,
FakeDisplayDevice displayDevice,
int modeId) {
@@ -1490,6 +1687,7 @@ public class DisplayManagerServiceTest {
new Display.Mode(i + 1, width, height, refreshRates[i]);
}
displayDeviceInfo.modeId = 1;
+ displayDeviceInfo.renderFrameRate = displayDeviceInfo.supportedModes[0].getRefreshRate();
displayDeviceInfo.width = width;
displayDeviceInfo.height = height;
final Rect zeroRect = new Rect();
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 71f3d15e76fb..865bc987cb2c 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
@@ -200,12 +200,7 @@ public class DisplayModeDirectorTest {
}
@Test
- @Parameters({
- "true",
- "false"
- })
- public void testDisplayModeVoting(boolean frameRateIsRefreshRate) {
- when(mInjector.renderFrameRateIsPhysicalRefreshRate()).thenReturn(frameRateIsRefreshRate);
+ public void testDisplayModeVoting() {
// With no votes present, DisplayModeDirector should allow any refresh rate.
DisplayModeDirector director = createDirectorFromFpsRange(60, 90);
DesiredDisplayModeSpecs modeSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
@@ -242,12 +237,7 @@ public class DisplayModeDirectorTest {
.isEqualTo((float) (minFps + i));
assertThat(modeSpecs.primary.physical.max)
.isEqualTo((float) (maxFps - i));
- if (frameRateIsRefreshRate) {
- assertThat(modeSpecs.primary.render.min)
- .isEqualTo((float) (minFps + i));
- } else {
- assertThat(modeSpecs.primary.render.min).isZero();
- }
+ assertThat(modeSpecs.primary.render.min).isZero();
assertThat(modeSpecs.primary.render.max)
.isEqualTo((float) (maxFps - i));
if (priority >= Vote.APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF) {
@@ -255,12 +245,7 @@ public class DisplayModeDirectorTest {
.isEqualTo((float) (minFps + i));
assertThat(modeSpecs.appRequest.physical.max)
.isEqualTo((float) (maxFps - i));
- if (frameRateIsRefreshRate) {
- assertThat(modeSpecs.appRequest.render.min).isEqualTo(
- (float) (minFps + i));
- } else {
- assertThat(modeSpecs.appRequest.render.min).isZero();
- }
+ assertThat(modeSpecs.appRequest.render.min).isZero();
assertThat(modeSpecs.appRequest.render.max).isEqualTo(
(float) (maxFps - i));
} else {
@@ -292,12 +277,7 @@ public class DisplayModeDirectorTest {
}
@Test
- @Parameters({
- "true",
- "false"
- })
- public void testVotingWithFloatingPointErrors(boolean frameRateIsRefreshRate) {
- when(mInjector.renderFrameRateIsPhysicalRefreshRate()).thenReturn(frameRateIsRefreshRate);
+ public void testVotingWithFloatingPointErrors() {
DisplayModeDirector director = createDirectorFromFpsRange(50, 90);
SparseArray<Vote> votes = new SparseArray<>();
SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
@@ -318,12 +298,7 @@ public class DisplayModeDirectorTest {
}
@Test
- @Parameters({
- "true",
- "false"
- })
- public void testFlickerHasLowerPriorityThanUserAndRangeIsSingle(
- boolean frameRateIsRefreshRate) {
+ public void testFlickerHasLowerPriorityThanUserAndRangeIsSingle() {
assertTrue(Vote.PRIORITY_FLICKER_REFRESH_RATE
< Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE);
assertTrue(Vote.PRIORITY_FLICKER_REFRESH_RATE
@@ -332,7 +307,6 @@ public class DisplayModeDirectorTest {
assertTrue(Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH
> Vote.PRIORITY_LOW_POWER_MODE);
- when(mInjector.renderFrameRateIsPhysicalRefreshRate()).thenReturn(frameRateIsRefreshRate);
Display.Mode[] modes = new Display.Mode[4];
modes[0] = new Display.Mode(
/*modeId=*/1, /*width=*/1000, /*height=*/1000, 60);
@@ -408,17 +382,12 @@ public class DisplayModeDirectorTest {
}
@Test
- @Parameters({
- "true",
- "false"
- })
- public void testLPMHasHigherPriorityThanUser(boolean frameRateIsRefreshRate) {
+ public void testLPMHasHigherPriorityThanUser() {
assertTrue(Vote.PRIORITY_LOW_POWER_MODE
> Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE);
assertTrue(Vote.PRIORITY_LOW_POWER_MODE
> Vote.PRIORITY_APP_REQUEST_SIZE);
- when(mInjector.renderFrameRateIsPhysicalRefreshRate()).thenReturn(frameRateIsRefreshRate);
Display.Mode[] modes = new Display.Mode[4];
modes[0] = new Display.Mode(
/*modeId=*/1, /*width=*/1000, /*height=*/1000, 60);
@@ -443,11 +412,7 @@ public class DisplayModeDirectorTest {
DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
assertThat(desiredSpecs.baseModeId).isEqualTo(2);
assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(60);
- if (frameRateIsRefreshRate) {
- assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(60);
- } else {
- assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity();
- }
+ assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity();
assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(60);
assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(60);
@@ -462,11 +427,7 @@ public class DisplayModeDirectorTest {
desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
assertThat(desiredSpecs.baseModeId).isEqualTo(4);
assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(90);
- if (frameRateIsRefreshRate) {
- assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(90);
- } else {
- assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity();
- }
+ assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity();
assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(90);
assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(90);
@@ -481,11 +442,7 @@ public class DisplayModeDirectorTest {
desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
assertThat(desiredSpecs.baseModeId).isEqualTo(2);
assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(60);
- if (frameRateIsRefreshRate) {
- assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(60);
- } else {
- assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity();
- }
+ assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity();
assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(60);
assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(60);
@@ -500,21 +457,13 @@ public class DisplayModeDirectorTest {
desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
assertThat(desiredSpecs.baseModeId).isEqualTo(4);
assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(90);
- if (frameRateIsRefreshRate) {
- assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(90);
- } else {
- assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity();
- }
+ assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity();
assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(90);
assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(90);
}
@Test
- @Parameters({
- "true",
- "false"
- })
- public void testAppRequestRefreshRateRange(boolean frameRateIsRefreshRate) {
+ public void testAppRequestRefreshRateRange() {
// Confirm that the app request range doesn't include flicker or min refresh rate settings,
// but does include everything else.
assertTrue(
@@ -525,7 +474,6 @@ public class DisplayModeDirectorTest {
assertTrue(Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE
>= Vote.APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF);
- when(mInjector.renderFrameRateIsPhysicalRefreshRate()).thenReturn(frameRateIsRefreshRate);
Display.Mode[] modes = new Display.Mode[3];
modes[0] = new Display.Mode(
/*modeId=*/60, /*width=*/1000, /*height=*/1000, 60);
@@ -582,12 +530,7 @@ public class DisplayModeDirectorTest {
}
@Test
- @Parameters({
- "true",
- "false"
- })
- public void testSpecsFromRefreshRateSettings(boolean frameRateIsRefreshRate) {
- when(mInjector.renderFrameRateIsPhysicalRefreshRate()).thenReturn(frameRateIsRefreshRate);
+ public void testSpecsFromRefreshRateSettings() {
// Confirm that, with varying settings for min, peak, and default refresh rate,
// DesiredDisplayModeSpecs is calculated correctly.
float[] refreshRates = {30.f, 60.f, 90.f, 120.f, 150.f};
@@ -607,27 +550,12 @@ public class DisplayModeDirectorTest {
RefreshRateRanges frameRateAll = new RefreshRateRanges(rangeAll, rangeAll);
RefreshRateRanges frameRate90toInf = new RefreshRateRanges(range90toInf, range90toInf);
- RefreshRateRanges frameRate0to60;
- RefreshRateRanges frameRate0to90;
- RefreshRateRanges frameRate0to120;
- RefreshRateRanges frameRate60to90;
- RefreshRateRanges frameRate90to90;
- RefreshRateRanges frameRate90to120;
- if (frameRateIsRefreshRate) {
- frameRate0to60 = new RefreshRateRanges(range0to60, range0to60);
- frameRate0to90 = new RefreshRateRanges(range0to90, range0to90);
- frameRate0to120 = new RefreshRateRanges(range0to120, range0to120);
- frameRate60to90 = new RefreshRateRanges(range60to90, range60to90);
- frameRate90to90 = new RefreshRateRanges(range90to90, range90to90);
- frameRate90to120 = new RefreshRateRanges(range90to120, range90to120);
- } else {
- frameRate0to60 = new RefreshRateRanges(rangeAll, range0to60);
- frameRate0to90 = new RefreshRateRanges(rangeAll, range0to90);
- frameRate0to120 = new RefreshRateRanges(rangeAll, range0to120);
- frameRate60to90 = new RefreshRateRanges(range60toInf, range60to90);
- frameRate90to90 = new RefreshRateRanges(range90toInf, range90to90);
- frameRate90to120 = new RefreshRateRanges(range90toInf, range90to120);
- }
+ RefreshRateRanges frameRate0to60 = new RefreshRateRanges(rangeAll, range0to60);
+ RefreshRateRanges frameRate0to90 = new RefreshRateRanges(rangeAll, range0to90);
+ RefreshRateRanges frameRate0to120 = new RefreshRateRanges(rangeAll, range0to120);
+ RefreshRateRanges frameRate60to90 = new RefreshRateRanges(range60toInf, range60to90);
+ RefreshRateRanges frameRate90to90 = new RefreshRateRanges(range90toInf, range90to90);
+ RefreshRateRanges frameRate90to120 = new RefreshRateRanges(range90toInf, range90to120);
verifySpecsWithRefreshRateSettings(director, 0, 0, 0, frameRateAll, frameRateAll);
verifySpecsWithRefreshRateSettings(director, 0, 0, 90, frameRate0to90, frameRateAll);
@@ -657,12 +585,7 @@ public class DisplayModeDirectorTest {
}
@Test
- @Parameters({
- "true",
- "false"
- })
- public void testBrightnessObserverCallWithRefreshRateSettings(boolean frameRateIsRefreshRate) {
- when(mInjector.renderFrameRateIsPhysicalRefreshRate()).thenReturn(frameRateIsRefreshRate);
+ public void testBrightnessObserverCallWithRefreshRateSettings() {
// Confirm that, with varying settings for min, peak, and default refresh rate, we make the
// correct call to the brightness observer.
float[] refreshRates = {60.f, 90.f, 120.f};
@@ -677,12 +600,7 @@ public class DisplayModeDirectorTest {
}
@Test
- @Parameters({
- "true",
- "false"
- })
- public void testVotingWithAlwaysRespectAppRequest(boolean frameRateIsRefreshRate) {
- when(mInjector.renderFrameRateIsPhysicalRefreshRate()).thenReturn(frameRateIsRefreshRate);
+ public void testVotingWithAlwaysRespectAppRequest() {
Display.Mode[] modes = new Display.Mode[3];
modes[0] = new Display.Mode(
/*modeId=*/50, /*width=*/1000, /*height=*/1000, 50);
@@ -711,11 +629,7 @@ public class DisplayModeDirectorTest {
DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(60);
- if (frameRateIsRefreshRate) {
- assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(60);
- } else {
- assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity();
- }
+ assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity();
assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(60);
assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(60);
assertThat(desiredSpecs.baseModeId).isEqualTo(60);
@@ -734,23 +648,14 @@ public class DisplayModeDirectorTest {
desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(60);
- if (frameRateIsRefreshRate) {
- assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(60);
- } else {
- assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity();
- }
+ assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity();
assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(60);
assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(60);
assertThat(desiredSpecs.baseModeId).isEqualTo(60);
}
@Test
- @Parameters({
- "true",
- "false"
- })
- public void testVotingWithSwitchingTypeNone(boolean frameRateIsRefreshRate) {
- when(mInjector.renderFrameRateIsPhysicalRefreshRate()).thenReturn(frameRateIsRefreshRate);
+ public void testVotingWithSwitchingTypeNone() {
DisplayModeDirector director = createDirectorFromFpsRange(0, 90);
SparseArray<Vote> votes = new SparseArray<>();
SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
@@ -765,20 +670,11 @@ public class DisplayModeDirectorTest {
DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(30);
- if (frameRateIsRefreshRate) {
- assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(60);
- } else {
- assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity();
- }
+ assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity();
assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(30);
assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(60);
assertThat(desiredSpecs.appRequest.physical.min).isWithin(FLOAT_TOLERANCE).of(0);
- if (frameRateIsRefreshRate) {
- assertThat(desiredSpecs.appRequest.physical.max).isWithin(FLOAT_TOLERANCE).of(
- 60);
- } else {
- assertThat(desiredSpecs.appRequest.physical.max).isPositiveInfinity();
- }
+ assertThat(desiredSpecs.appRequest.physical.max).isPositiveInfinity();
assertThat(desiredSpecs.appRequest.render.min).isWithin(FLOAT_TOLERANCE).of(0);
assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(60);
assertThat(desiredSpecs.baseModeId).isEqualTo(30);
@@ -800,12 +696,7 @@ public class DisplayModeDirectorTest {
}
@Test
- @Parameters({
- "true",
- "false"
- })
- public void testVotingWithSwitchingTypeRenderFrameRateOnly(boolean frameRateIsRefreshRate) {
- when(mInjector.renderFrameRateIsPhysicalRefreshRate()).thenReturn(frameRateIsRefreshRate);
+ public void testVotingWithSwitchingTypeRenderFrameRateOnly() {
DisplayModeDirector director = createDirectorFromFpsRange(0, 90);
SparseArray<Vote> votes = new SparseArray<>();
SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
@@ -816,24 +707,15 @@ public class DisplayModeDirectorTest {
director.injectVotesByDisplay(votesByDisplay);
assertThat(director.getModeSwitchingType())
- .isNotEqualTo(DisplayManager.SWITCHING_TYPE_NONE);
+ .isNotEqualTo(DisplayManager.SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY);
DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(30);
- if (frameRateIsRefreshRate) {
- assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(60);
- } else {
- assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity();
- }
+ assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity();
assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(30);
assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(60);
assertThat(desiredSpecs.appRequest.physical.min).isWithin(FLOAT_TOLERANCE).of(0);
- if (frameRateIsRefreshRate) {
- assertThat(desiredSpecs.appRequest.physical.max).isWithin(FLOAT_TOLERANCE).of(
- 60);
- } else {
- assertThat(desiredSpecs.appRequest.physical.max).isPositiveInfinity();
- }
+ assertThat(desiredSpecs.appRequest.physical.max).isPositiveInfinity();
assertThat(desiredSpecs.appRequest.render.min).isWithin(FLOAT_TOLERANCE).of(0);
assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(60);
assertThat(desiredSpecs.baseModeId).isEqualTo(30);
@@ -846,25 +728,58 @@ public class DisplayModeDirectorTest {
assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(30);
assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(30);
assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(30);
- if (frameRateIsRefreshRate) {
- assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(30);
- } else {
- assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(60);
- }
+ assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(30);
assertThat(desiredSpecs.appRequest.physical.min).isWithin(FLOAT_TOLERANCE).of(30);
assertThat(desiredSpecs.appRequest.physical.max).isWithin(FLOAT_TOLERANCE).of(30);
- if (frameRateIsRefreshRate) {
- assertThat(desiredSpecs.appRequest.render.min).isWithin(FLOAT_TOLERANCE).of(30);
- assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(30);
- } else {
- assertThat(desiredSpecs.appRequest.render.min).isWithin(FLOAT_TOLERANCE).of(0);
- assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(60);
- }
+ assertThat(desiredSpecs.appRequest.render.min).isWithin(FLOAT_TOLERANCE).of(0);
+ assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(30);
assertThat(desiredSpecs.baseModeId).isEqualTo(30);
}
@Test
+ public void testVotingWithSwitchingTypeRenderFrameRateOnlyRenderRateIsNotPhysicalRefreshRate() {
+ DisplayModeDirector director = createDirectorFromFpsRange(90, 120);
+ SparseArray<Vote> votes = new SparseArray<>();
+ SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
+ votesByDisplay.put(DISPLAY_ID, votes);
+ votes.put(Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE,
+ Vote.forRenderFrameRates(30, 90));
+ votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 60));
+
+ director.injectVotesByDisplay(votesByDisplay);
+ assertThat(director.getModeSwitchingType())
+ .isNotEqualTo(DisplayManager.SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY);
+ DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
+
+ assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(30);
+ assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity();
+ assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(30);
+ assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(60);
+ assertThat(desiredSpecs.appRequest.physical.min).isWithin(FLOAT_TOLERANCE).of(0);
+ assertThat(desiredSpecs.appRequest.physical.max).isPositiveInfinity();
+ assertThat(desiredSpecs.appRequest.render.min).isWithin(FLOAT_TOLERANCE).of(0);
+ assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(60);
+ assertThat(desiredSpecs.baseModeId).isEqualTo(90);
+
+ director.setModeSwitchingType(DisplayManager.SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY);
+ assertThat(director.getModeSwitchingType())
+ .isEqualTo(DisplayManager.SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY);
+
+ desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
+ assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(90);
+ assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(90);
+ assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(60);
+ assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(60);
+ assertThat(desiredSpecs.appRequest.physical.min).isWithin(FLOAT_TOLERANCE).of(90);
+ assertThat(desiredSpecs.appRequest.physical.max).isWithin(FLOAT_TOLERANCE).of(90);
+ assertThat(desiredSpecs.appRequest.render.min).isWithin(FLOAT_TOLERANCE).of(0);
+ assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(60);
+
+ assertThat(desiredSpecs.baseModeId).isEqualTo(90);
+ }
+
+ @Test
public void testVotingWithSwitchingTypeWithinGroups() {
DisplayModeDirector director = createDirectorFromFpsRange(0, 90);
@@ -887,12 +802,7 @@ public class DisplayModeDirectorTest {
}
@Test
- @Parameters({
- "true",
- "false"
- })
- public void testDefaultDisplayModeIsSelectedIfAvailable(boolean frameRateIsRefreshRate) {
- when(mInjector.renderFrameRateIsPhysicalRefreshRate()).thenReturn(frameRateIsRefreshRate);
+ public void testDefaultDisplayModeIsSelectedIfAvailable() {
final float[] refreshRates = new float[]{24f, 25f, 30f, 60f, 90f};
final int defaultModeId = 3;
DisplayModeDirector director = createDirectorFromRefreshRateArray(
@@ -1173,17 +1083,12 @@ public class DisplayModeDirectorTest {
}
@Test
- @Parameters({
- "true",
- "false"
- })
- public void testAppRequestMinRefreshRate(boolean frameRateIsRefreshRate) {
+ public void testAppRequestMinRefreshRate() {
// Confirm that the app min request range doesn't include flicker or min refresh rate
// settings but does include everything else.
assertTrue(Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE
>= Vote.APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF);
- when(mInjector.renderFrameRateIsPhysicalRefreshRate()).thenReturn(frameRateIsRefreshRate);
Display.Mode[] modes = new Display.Mode[3];
modes[0] = new Display.Mode(
/*modeId=*/60, /*width=*/1000, /*height=*/1000, 60);
@@ -1225,11 +1130,7 @@ public class DisplayModeDirectorTest {
}
@Test
- @Parameters({
- "true",
- "false"
- })
- public void testAppRequestMaxRefreshRate(boolean frameRateIsRefreshRate) {
+ public void testAppRequestMaxRefreshRate() {
// Confirm that the app max request range doesn't include flicker or min refresh rate
// settings but does include everything else.
assertTrue(Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE
@@ -1243,7 +1144,6 @@ public class DisplayModeDirectorTest {
modes[2] = new Display.Mode(
/*modeId=*/90, /*width=*/1000, /*height=*/1000, 90);
- when(mInjector.renderFrameRateIsPhysicalRefreshRate()).thenReturn(frameRateIsRefreshRate);
DisplayModeDirector director = createDirectorFromModeArray(modes, modes[1]);
SparseArray<Vote> votes = new SparseArray<>();
SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
@@ -1254,19 +1154,11 @@ public class DisplayModeDirectorTest {
DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(60);
assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(60);
- if (frameRateIsRefreshRate) {
- assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(60);
- } else {
- assertThat(desiredSpecs.primary.render.min).isZero();
- }
+ assertThat(desiredSpecs.primary.render.min).isZero();
assertThat(desiredSpecs.primary.render.max).isAtMost(60);
assertThat(desiredSpecs.appRequest.physical.min).isAtMost(60f);
assertThat(desiredSpecs.appRequest.physical.max).isAtLeast(90f);
- if (frameRateIsRefreshRate) {
- assertThat(desiredSpecs.appRequest.render.min).isAtMost(60f);
- } else {
- assertThat(desiredSpecs.appRequest.render.min).isZero();
- }
+ assertThat(desiredSpecs.appRequest.render.min).isZero();
assertThat(desiredSpecs.appRequest.render.max).isAtLeast(90f);
votes.put(Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE,
@@ -1288,30 +1180,16 @@ public class DisplayModeDirectorTest {
desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(75);
assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(75);
- if (frameRateIsRefreshRate) {
- assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(75);
- } else {
- assertThat(desiredSpecs.primary.render.min).isZero();
- }
+ assertThat(desiredSpecs.primary.render.min).isZero();
assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(75);
assertThat(desiredSpecs.appRequest.physical.min).isZero();
- if (frameRateIsRefreshRate) {
- assertThat(desiredSpecs.appRequest.physical.max).isWithin(FLOAT_TOLERANCE).of(
- 75);
- } else {
- assertThat(desiredSpecs.appRequest.physical.max).isAtLeast(90f);
- }
+ assertThat(desiredSpecs.appRequest.physical.max).isAtLeast(90f);
assertThat(desiredSpecs.appRequest.render.min).isZero();
assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(75);
}
@Test
- @Parameters({
- "true",
- "false"
- })
- public void testAppRequestObserver_modeId(boolean frameRateIsRefreshRate) {
- when(mInjector.renderFrameRateIsPhysicalRefreshRate()).thenReturn(frameRateIsRefreshRate);
+ public void testAppRequestObserver_modeId() {
DisplayModeDirector director = createDirectorFromFpsRange(60, 90);
director.getAppRequestObserver().setAppRequest(DISPLAY_ID, 60, 0, 0);
@@ -1373,12 +1251,7 @@ public class DisplayModeDirectorTest {
}
@Test
- @Parameters({
- "true",
- "false"
- })
- public void testAppRequestObserver_minRefreshRate(boolean frameRateIsRefreshRate) {
- when(mInjector.renderFrameRateIsPhysicalRefreshRate()).thenReturn(frameRateIsRefreshRate);
+ public void testAppRequestObserver_minRefreshRate() {
DisplayModeDirector director = createDirectorFromFpsRange(60, 90);
director.getAppRequestObserver().setAppRequest(DISPLAY_ID, -1, 60, 0);
Vote appRequestRefreshRate =
@@ -1391,15 +1264,9 @@ public class DisplayModeDirectorTest {
Vote appRequestRefreshRateRange =
director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE);
assertNotNull(appRequestRefreshRateRange);
- if (frameRateIsRefreshRate) {
- assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.min)
- .isWithin(FLOAT_TOLERANCE).of(60);
- assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.max).isAtLeast(90);
- } else {
- assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.min).isZero();
- assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.max)
- .isPositiveInfinity();
- }
+ assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.min).isZero();
+ assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.max)
+ .isPositiveInfinity();
assertThat(appRequestRefreshRateRange.refreshRateRanges.render.min)
.isWithin(FLOAT_TOLERANCE).of(60);
assertThat(appRequestRefreshRateRange.refreshRateRanges.render.max).isAtLeast(90);
@@ -1417,15 +1284,9 @@ public class DisplayModeDirectorTest {
appRequestRefreshRateRange =
director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE);
assertNotNull(appRequestRefreshRateRange);
- if (frameRateIsRefreshRate) {
- assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.min).isWithin(
- FLOAT_TOLERANCE).of(90);
- assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.max).isAtLeast(90);
- } else {
- assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.min).isZero();
- assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.max)
- .isPositiveInfinity();
- }
+ assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.min).isZero();
+ assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.max)
+ .isPositiveInfinity();
assertThat(appRequestRefreshRateRange.refreshRateRanges.render.min)
.isWithin(FLOAT_TOLERANCE).of(90);
@@ -1435,12 +1296,7 @@ public class DisplayModeDirectorTest {
}
@Test
- @Parameters({
- "true",
- "false"
- })
- public void testAppRequestObserver_maxRefreshRate(boolean frameRateIsRefreshRate) {
- when(mInjector.renderFrameRateIsPhysicalRefreshRate()).thenReturn(frameRateIsRefreshRate);
+ public void testAppRequestObserver_maxRefreshRate() {
DisplayModeDirector director = createDirectorFromFpsRange(60, 90);
director.getAppRequestObserver().setAppRequest(DISPLAY_ID, -1, 0, 90);
Vote appRequestRefreshRate =
@@ -1454,13 +1310,8 @@ public class DisplayModeDirectorTest {
director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE);
assertNotNull(appRequestRefreshRateRange);
assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.min).isZero();
- if (frameRateIsRefreshRate) {
- assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.max)
- .isWithin(FLOAT_TOLERANCE).of(90);
- } else {
- assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.max)
- .isPositiveInfinity();
- }
+ assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.max)
+ .isPositiveInfinity();
assertThat(appRequestRefreshRateRange.refreshRateRanges.render.min).isZero();
assertThat(appRequestRefreshRateRange.refreshRateRanges.render.max)
@@ -1480,13 +1331,8 @@ public class DisplayModeDirectorTest {
director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE);
assertNotNull(appRequestRefreshRateRange);
assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.min).isZero();
- if (frameRateIsRefreshRate) {
- assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.max)
- .isWithin(FLOAT_TOLERANCE).of(60);
- } else {
- assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.max)
- .isPositiveInfinity();
- }
+ assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.max)
+ .isPositiveInfinity();
assertThat(appRequestRefreshRateRange.refreshRateRanges.render.min).isZero();
assertThat(appRequestRefreshRateRange.refreshRateRanges.render.max)
@@ -1512,12 +1358,7 @@ public class DisplayModeDirectorTest {
}
@Test
- @Parameters({
- "true",
- "false"
- })
- public void testAppRequestObserver_modeIdAndRefreshRateRange(boolean frameRateIsRefreshRate) {
- when(mInjector.renderFrameRateIsPhysicalRefreshRate()).thenReturn(frameRateIsRefreshRate);
+ public void testAppRequestObserver_modeIdAndRefreshRateRange() {
DisplayModeDirector director = createDirectorFromFpsRange(60, 90);
director.getAppRequestObserver().setAppRequest(DISPLAY_ID, 60, 90, 90);
@@ -1547,16 +1388,9 @@ public class DisplayModeDirectorTest {
Vote appRequestRefreshRateRange =
director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE);
assertNotNull(appRequestRefreshRateRange);
- if (frameRateIsRefreshRate) {
- assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.min)
- .isWithin(FLOAT_TOLERANCE).of(90);
- assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.max)
- .isWithin(FLOAT_TOLERANCE).of(90);
- } else {
- assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.min).isZero();
- assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.max)
- .isPositiveInfinity();
- }
+ assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.min).isZero();
+ assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.max)
+ .isPositiveInfinity();
assertThat(appRequestRefreshRateRange.refreshRateRanges.render.min)
.isWithin(FLOAT_TOLERANCE).of(90);
assertThat(appRequestRefreshRateRange.refreshRateRanges.render.max)
@@ -1566,12 +1400,7 @@ public class DisplayModeDirectorTest {
}
@Test
- @Parameters({
- "true",
- "false"
- })
- public void testAppRequestsIsTheDefaultMode(boolean frameRateIsRefreshRate) {
- when(mInjector.renderFrameRateIsPhysicalRefreshRate()).thenReturn(frameRateIsRefreshRate);
+ public void testAppRequestsIsTheDefaultMode() {
Display.Mode[] modes = new Display.Mode[2];
modes[0] = new Display.Mode(
/*modeId=*/1, /*width=*/1000, /*height=*/1000, 60);
@@ -1600,12 +1429,7 @@ public class DisplayModeDirectorTest {
}
@Test
- @Parameters({
- "true",
- "false"
- })
- public void testDisableRefreshRateSwitchingVote(boolean frameRateIsRefreshRate) {
- when(mInjector.renderFrameRateIsPhysicalRefreshRate()).thenReturn(frameRateIsRefreshRate);
+ public void testDisableRefreshRateSwitchingVote() {
DisplayModeDirector director = createDirectorFromFpsRange(50, 90);
SparseArray<Vote> votes = new SparseArray<>();
SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
@@ -1650,8 +1474,8 @@ public class DisplayModeDirectorTest {
"true",
"false"
})
- public void testBaseModeIdInPrimaryRange(boolean frameRateIsRefreshRate) {
- when(mInjector.renderFrameRateIsPhysicalRefreshRate()).thenReturn(frameRateIsRefreshRate);
+ public void testBaseModeIdInPrimaryRange(boolean supportsFrameRateOverride) {
+ when(mInjector.supportsFrameRateOverride()).thenReturn(supportsFrameRateOverride);
DisplayModeDirector director = createDirectorFromFpsRange(50, 90);
SparseArray<Vote> votes = new SparseArray<>();
SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
@@ -1662,12 +1486,12 @@ public class DisplayModeDirectorTest {
director.injectVotesByDisplay(votesByDisplay);
DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(0);
- if (frameRateIsRefreshRate) {
- assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(60);
- assertThat(desiredSpecs.baseModeId).isEqualTo(50);
- } else {
- assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity();
+ assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity();
+ if (supportsFrameRateOverride) {
assertThat(desiredSpecs.baseModeId).isEqualTo(70);
+ } else {
+ assertThat(desiredSpecs.baseModeId).isEqualTo(50);
+
}
assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(0);
assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(60);
@@ -1679,11 +1503,7 @@ public class DisplayModeDirectorTest {
director.injectVotesByDisplay(votesByDisplay);
desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(0);
- if (frameRateIsRefreshRate) {
- assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(60);
- } else {
- assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity();
- }
+ assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity();
assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(0);
assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(60);
assertThat(desiredSpecs.baseModeId).isEqualTo(55);
@@ -1697,12 +1517,11 @@ public class DisplayModeDirectorTest {
director.injectVotesByDisplay(votesByDisplay);
desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(0);
- if (frameRateIsRefreshRate) {
- assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(60);
- assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(60);
- } else {
- assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity();
+ assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity();
+ if (supportsFrameRateOverride) {
assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(52);
+ } else {
+ assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(60);
}
assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(0);
assertThat(desiredSpecs.baseModeId).isEqualTo(55);
@@ -1716,23 +1535,14 @@ public class DisplayModeDirectorTest {
director.injectVotesByDisplay(votesByDisplay);
desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(0);
- if (frameRateIsRefreshRate) {
- assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(58);
- } else {
- assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity();
- }
+ assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity();
assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(0);
assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(58);
assertThat(desiredSpecs.baseModeId).isEqualTo(55);
}
@Test
- @Parameters({
- "true",
- "false"
- })
- public void testStaleAppVote(boolean frameRateIsRefreshRate) {
- when(mInjector.renderFrameRateIsPhysicalRefreshRate()).thenReturn(frameRateIsRefreshRate);
+ public void testStaleAppVote() {
Display.Mode[] modes = new Display.Mode[4];
modes[0] = new Display.Mode(
/*modeId=*/1, /*width=*/1000, /*height=*/1000, 60);
@@ -1782,8 +1592,8 @@ public class DisplayModeDirectorTest {
"true",
"false"
})
- public void testRefreshRateIsSubsetOfFrameRate(boolean frameRateIsRefreshRate) {
- when(mInjector.renderFrameRateIsPhysicalRefreshRate()).thenReturn(frameRateIsRefreshRate);
+ public void testRefreshRateIsSubsetOfFrameRate(boolean supportsFrameRateOverride) {
+ when(mInjector.supportsFrameRateOverride()).thenReturn(supportsFrameRateOverride);
DisplayModeDirector director = createDirectorFromFpsRange(60, 120);
SparseArray<Vote> votes = new SparseArray<>();
SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
@@ -1795,11 +1605,7 @@ public class DisplayModeDirectorTest {
DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
assertThat(desiredSpecs.appRequest.physical.min).isWithin(FLOAT_TOLERANCE).of(90);
assertThat(desiredSpecs.appRequest.physical.max).isWithin(FLOAT_TOLERANCE).of(120);
- if (frameRateIsRefreshRate) {
- assertThat(desiredSpecs.appRequest.render.min).isWithin(FLOAT_TOLERANCE).of(90);
- } else {
- assertThat(desiredSpecs.appRequest.render.min).isZero();
- }
+ assertThat(desiredSpecs.appRequest.render.min).isZero();
assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(120);
votes.clear();
@@ -1810,13 +1616,11 @@ public class DisplayModeDirectorTest {
desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
assertThat(desiredSpecs.appRequest.physical.min).isWithin(FLOAT_TOLERANCE).of(90);
assertThat(desiredSpecs.appRequest.physical.max).isWithin(FLOAT_TOLERANCE).of(120);
- if (frameRateIsRefreshRate) {
- assertThat(desiredSpecs.appRequest.render.min).isWithin(FLOAT_TOLERANCE).of(90);
- assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(
- 120);
- } else {
- assertThat(desiredSpecs.appRequest.render.min).isZero();
+ assertThat(desiredSpecs.appRequest.render.min).isZero();
+ if (supportsFrameRateOverride) {
assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(60);
+ } else {
+ assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(120);
}
votes.clear();
@@ -1827,13 +1631,12 @@ public class DisplayModeDirectorTest {
desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
assertThat(desiredSpecs.appRequest.physical.min).isWithin(FLOAT_TOLERANCE).of(90);
assertThat(desiredSpecs.appRequest.physical.max).isWithin(FLOAT_TOLERANCE).of(120);
- if (frameRateIsRefreshRate) {
- assertThat(desiredSpecs.appRequest.render.min).isWithin(FLOAT_TOLERANCE).of(90);
- assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(
- 120);
- } else {
+ if (supportsFrameRateOverride) {
assertThat(desiredSpecs.appRequest.render.min).isWithin(FLOAT_TOLERANCE).of(60);
assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(60);
+ } else {
+ assertThat(desiredSpecs.appRequest.render.min).isZero();
+ assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(120);
}
votes.clear();
@@ -1844,17 +1647,12 @@ public class DisplayModeDirectorTest {
desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
assertThat(desiredSpecs.appRequest.physical.min).isWithin(FLOAT_TOLERANCE).of(90);
assertThat(desiredSpecs.appRequest.physical.max).isWithin(FLOAT_TOLERANCE).of(120);
- if (frameRateIsRefreshRate) {
- assertThat(desiredSpecs.appRequest.render.min).isWithin(FLOAT_TOLERANCE).of(90);
- } else {
- assertThat(desiredSpecs.appRequest.render.min).isZero();
- }
+ assertThat(desiredSpecs.appRequest.render.min).isZero();
assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(120);
}
@Test
public void testRenderFrameRateIsAchievableByPhysicalRefreshRate() {
- when(mInjector.renderFrameRateIsPhysicalRefreshRate()).thenReturn(false);
DisplayModeDirector director = createDirectorFromFpsRange(60, 120);
SparseArray<Vote> votes = new SparseArray<>();
SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
@@ -1872,8 +1670,34 @@ public class DisplayModeDirectorTest {
}
@Test
+ @Parameters({
+ "true",
+ "false"
+ })
+ public void testRenderFrameRateIncludesPhysicalRefreshRate(boolean supportsFrameRateOverride) {
+ when(mInjector.supportsFrameRateOverride()).thenReturn(supportsFrameRateOverride);
+ DisplayModeDirector director = createDirectorFromFpsRange(60, 120);
+ SparseArray<Vote> votes = new SparseArray<>();
+ SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
+ votesByDisplay.put(DISPLAY_ID, votes);
+
+ votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 60));
+ votes.put(Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE,
+ Vote.forRenderFrameRates(0, 30));
+ director.injectVotesByDisplay(votesByDisplay);
+ DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
+ assertThat(desiredSpecs.appRequest.physical.min).isZero();
+ assertThat(desiredSpecs.appRequest.physical.max).isPositiveInfinity();
+ assertThat(desiredSpecs.appRequest.render.min).isZero();
+ if (supportsFrameRateOverride) {
+ assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(30);
+ } else {
+ assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(60);
+ }
+ }
+
+ @Test
public void testRenderFrameRateIsDroppedIfLowerPriorityThenBaseModeRefreshRate() {
- when(mInjector.renderFrameRateIsPhysicalRefreshRate()).thenReturn(false);
DisplayModeDirector director = createDirectorFromFpsRange(60, 120);
SparseArray<Vote> votes = new SparseArray<>();
SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
@@ -2692,7 +2516,7 @@ public class DisplayModeDirectorTest {
}
@Override
- public boolean renderFrameRateIsPhysicalRefreshRate() {
+ public boolean supportsFrameRateOverride() {
return true;
}
diff --git a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
index 246945c2d968..c7caa4372b0e 100644
--- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
@@ -54,6 +54,7 @@ import android.view.DisplayInfo;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.server.display.layout.DisplayIdProducer;
import com.android.server.display.layout.Layout;
import org.junit.Before;
@@ -76,6 +77,7 @@ public class LogicalDisplayMapperTest {
private static int sUniqueTestDisplayId = 0;
private static final int DEVICE_STATE_CLOSED = 0;
private static final int DEVICE_STATE_OPEN = 2;
+ private static int sNextNonDefaultDisplayId = DEFAULT_DISPLAY + 1;
private DisplayDeviceRepository mDisplayDeviceRepo;
private LogicalDisplayMapper mLogicalDisplayMapper;
@@ -83,12 +85,16 @@ public class LogicalDisplayMapperTest {
private Handler mHandler;
private PowerManager mPowerManager;
+ private final DisplayIdProducer mIdProducer = (isDefault) ->
+ isDefault ? DEFAULT_DISPLAY : sNextNonDefaultDisplayId++;
+
@Mock LogicalDisplayMapper.Listener mListenerMock;
@Mock Context mContextMock;
@Mock Resources mResourcesMock;
@Mock IPowerManager mIPowerManagerMock;
@Mock IThermalService mIThermalServiceMock;
- @Spy DeviceStateToLayoutMap mDeviceStateToLayoutMapSpy = new DeviceStateToLayoutMap();
+ @Spy DeviceStateToLayoutMap mDeviceStateToLayoutMapSpy =
+ new DeviceStateToLayoutMap(mIdProducer);
@Captor ArgumentCaptor<LogicalDisplay> mDisplayCaptor;
@@ -519,13 +525,17 @@ public class LogicalDisplayMapperTest {
DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
Layout layout = new Layout();
- layout.createDisplayLocked(device1.getDisplayDeviceInfoLocked().address, true, true);
- layout.createDisplayLocked(device2.getDisplayDeviceInfoLocked().address, false, false);
+ layout.createDisplayLocked(device1.getDisplayDeviceInfoLocked().address,
+ true, true, mIdProducer);
+ layout.createDisplayLocked(device2.getDisplayDeviceInfoLocked().address,
+ false, false, mIdProducer);
when(mDeviceStateToLayoutMapSpy.get(0)).thenReturn(layout);
layout = new Layout();
- layout.createDisplayLocked(device1.getDisplayDeviceInfoLocked().address, false, false);
- layout.createDisplayLocked(device2.getDisplayDeviceInfoLocked().address, true, true);
+ layout.createDisplayLocked(device1.getDisplayDeviceInfoLocked().address,
+ false, false, mIdProducer);
+ layout.createDisplayLocked(device2.getDisplayDeviceInfoLocked().address,
+ true, true, mIdProducer);
when(mDeviceStateToLayoutMapSpy.get(1)).thenReturn(layout);
when(mDeviceStateToLayoutMapSpy.get(2)).thenReturn(layout);
@@ -580,15 +590,18 @@ public class LogicalDisplayMapperTest {
threeDevicesEnabledLayout.createDisplayLocked(
displayAddressOne,
/* isDefault= */ true,
- /* isEnabled= */ true);
+ /* isEnabled= */ true,
+ mIdProducer);
threeDevicesEnabledLayout.createDisplayLocked(
displayAddressTwo,
/* isDefault= */ false,
- /* isEnabled= */ true);
+ /* isEnabled= */ true,
+ mIdProducer);
threeDevicesEnabledLayout.createDisplayLocked(
displayAddressThree,
/* isDefault= */ false,
- /* isEnabled= */ true);
+ /* isEnabled= */ true,
+ mIdProducer);
when(mDeviceStateToLayoutMapSpy.get(DeviceStateToLayoutMap.STATE_DEFAULT))
.thenReturn(threeDevicesEnabledLayout);
@@ -622,15 +635,18 @@ public class LogicalDisplayMapperTest {
oneDeviceEnabledLayout.createDisplayLocked(
displayAddressOne,
/* isDefault= */ true,
- /* isEnabled= */ true);
+ /* isEnabled= */ true,
+ mIdProducer);
oneDeviceEnabledLayout.createDisplayLocked(
displayAddressTwo,
/* isDefault= */ false,
- /* isEnabled= */ false);
+ /* isEnabled= */ false,
+ mIdProducer);
oneDeviceEnabledLayout.createDisplayLocked(
displayAddressThree,
/* isDefault= */ false,
- /* isEnabled= */ false);
+ /* isEnabled= */ false,
+ mIdProducer);
when(mDeviceStateToLayoutMapSpy.get(0)).thenReturn(oneDeviceEnabledLayout);
when(mDeviceStateToLayoutMapSpy.get(1)).thenReturn(threeDevicesEnabledLayout);
diff --git a/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java b/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java
index 3b0a22f80c30..35a677e0f816 100644
--- a/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java
@@ -344,6 +344,40 @@ public class PersistentDataStoreTest {
assertEquals(85.3f, newDataStore.getUserPreferredRefreshRate(testDisplayDevice), 0.1f);
}
+ @Test
+ public void testBrightnessInitialisesWithInvalidFloat() {
+ final String uniqueDisplayId = "test:123";
+ DisplayDevice testDisplayDevice = new DisplayDevice(null, null, uniqueDisplayId, null) {
+ @Override
+ public boolean hasStableUniqueId() {
+ return true;
+ }
+
+ @Override
+ public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
+ return null;
+ }
+ };
+
+ // Set any value which initialises Display state
+ float refreshRate = 85.3f;
+ mDataStore.loadIfNeeded();
+ mDataStore.setUserPreferredRefreshRate(testDisplayDevice, refreshRate);
+
+ final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ mInjector.setWriteStream(baos);
+ mDataStore.saveIfNeeded();
+ mTestLooper.dispatchAll();
+ assertTrue(mInjector.wasWriteSuccessful());
+ TestInjector newInjector = new TestInjector();
+ PersistentDataStore newDataStore = new PersistentDataStore(newInjector);
+ ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+ newInjector.setReadStream(bais);
+ newDataStore.loadIfNeeded();
+ assertTrue(Float.isNaN(mDataStore.getBrightness(testDisplayDevice)));
+ }
+
+
public class TestInjector extends PersistentDataStore.Injector {
private InputStream mReadStream;
private OutputStream mWriteStream;
diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java b/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
index a5d7a109eb83..dcf217c35c18 100644
--- a/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
@@ -29,6 +29,7 @@ import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.R;
+import com.android.server.display.brightness.strategy.BoostBrightnessStrategy;
import com.android.server.display.brightness.strategy.DozeBrightnessStrategy;
import com.android.server.display.brightness.strategy.InvalidBrightnessStrategy;
import com.android.server.display.brightness.strategy.OverrideBrightnessStrategy;
@@ -56,6 +57,8 @@ public final class DisplayBrightnessStrategySelectorTest {
@Mock
private TemporaryBrightnessStrategy mTemporaryBrightnessStrategy;
@Mock
+ private BoostBrightnessStrategy mBoostBrightnessStrategy;
+ @Mock
private InvalidBrightnessStrategy mInvalidBrightnessStrategy;
@Mock
private Context mContext;
@@ -92,6 +95,11 @@ public final class DisplayBrightnessStrategySelectorTest {
}
@Override
+ BoostBrightnessStrategy getBoostBrightnessStrategy() {
+ return mBoostBrightnessStrategy;
+ }
+
+ @Override
InvalidBrightnessStrategy getInvalidBrightnessStrategy() {
return mInvalidBrightnessStrategy;
}
@@ -140,6 +148,17 @@ public final class DisplayBrightnessStrategySelectorTest {
}
@Test
+ public void selectStrategySelectsBoostStrategyWhenValid() {
+ DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
+ DisplayManagerInternal.DisplayPowerRequest.class);
+ displayPowerRequest.boostScreenBrightness = true;
+ displayPowerRequest.screenBrightnessOverride = Float.NaN;
+ when(mTemporaryBrightnessStrategy.getTemporaryScreenBrightness()).thenReturn(Float.NaN);
+ assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest,
+ Display.STATE_ON), mBoostBrightnessStrategy);
+ }
+
+ @Test
public void selectStrategySelectsInvalidStrategyWhenNoStrategyIsValid() {
DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
DisplayManagerInternal.DisplayPowerRequest.class);
diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/strategy/BoostBrightnessStrategyTest.java b/services/tests/servicestests/src/com/android/server/display/brightness/strategy/BoostBrightnessStrategyTest.java
new file mode 100644
index 000000000000..431a239ab4fd
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/display/brightness/strategy/BoostBrightnessStrategyTest.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.brightness.strategy;
+
+
+import static org.junit.Assert.assertEquals;
+
+import android.hardware.display.DisplayManagerInternal;
+import android.os.PowerManager;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.display.DisplayBrightnessState;
+import com.android.server.display.brightness.BrightnessReason;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+
+public class BoostBrightnessStrategyTest {
+ private BoostBrightnessStrategy mBoostBrightnessStrategy;
+
+ @Before
+ public void before() {
+ mBoostBrightnessStrategy = new BoostBrightnessStrategy();
+ }
+
+ @Test
+ public void updateBrightnessWorksAsExpectedWhenBoostBrightnessIsRequested() {
+ DisplayManagerInternal.DisplayPowerRequest
+ displayPowerRequest = new DisplayManagerInternal.DisplayPowerRequest();
+ displayPowerRequest.boostScreenBrightness = true;
+ BrightnessReason brightnessReason = new BrightnessReason();
+ brightnessReason.setReason(BrightnessReason.REASON_BOOST);
+ DisplayBrightnessState expectedDisplayBrightnessState =
+ new DisplayBrightnessState.Builder()
+ .setBrightness(PowerManager.BRIGHTNESS_MAX)
+ .setBrightnessReason(brightnessReason)
+ .setSdrBrightness(PowerManager.BRIGHTNESS_MAX)
+ .build();
+ DisplayBrightnessState updatedDisplayBrightnessState =
+ mBoostBrightnessStrategy.updateBrightness(displayPowerRequest);
+ assertEquals(updatedDisplayBrightnessState, expectedDisplayBrightnessState);
+ }
+
+}
diff --git a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
index 9672085b8f3a..68e5ebf027ad 100644
--- a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
+++ b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
@@ -109,17 +109,16 @@ public final class UpdatableFontDirTest {
@Override
public boolean isFromTrustedProvider(String path, byte[] signature) {
- return mHasFsverityPaths.contains(path);
+ if (!mHasFsverityPaths.contains(path)) {
+ return false;
+ }
+ String fakeSignature = new String(signature, StandardCharsets.UTF_8);
+ return GOOD_SIGNATURE.equals(fakeSignature);
}
@Override
- public void setUpFsverity(String path, byte[] pkcs7Signature) throws IOException {
- String fakeSignature = new String(pkcs7Signature, StandardCharsets.UTF_8);
- if (GOOD_SIGNATURE.equals(fakeSignature)) {
- mHasFsverityPaths.add(path);
- } else {
- throw new IOException("Failed to set up fake fs-verity");
- }
+ public void setUpFsverity(String path) throws IOException {
+ mHasFsverityPaths.add(path);
}
@Override
@@ -813,8 +812,8 @@ public final class UpdatableFontDirTest {
}
@Override
- public void setUpFsverity(String path, byte[] pkcs7Signature) throws IOException {
- mFakeFsverityUtil.setUpFsverity(path, pkcs7Signature);
+ public void setUpFsverity(String path) throws IOException {
+ mFakeFsverityUtil.setUpFsverity(path);
}
@Override
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 09cd47a99cba..2cb46da9d207 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java
@@ -24,6 +24,8 @@ import static org.mockito.Mockito.spy;
import android.content.Context;
import android.content.ContextWrapper;
+import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.IHdmiControlCallback;
import android.hardware.tv.cec.V1_0.SendMessageResult;
import android.media.AudioManager;
import android.os.Looper;
@@ -52,6 +54,7 @@ public class ArcTerminationActionFromAvrTest {
private Context mContextSpy;
private HdmiCecLocalDeviceAudioSystem mHdmiCecLocalDeviceAudioSystem;
private FakePowerManagerWrapper mPowerManager;
+ private TestCallback mCallback;
private ArcTerminationActionFromAvr mAction;
private FakeNativeWrapper mNativeWrapper;
@@ -112,7 +115,9 @@ public class ArcTerminationActionFromAvrTest {
}
};
mHdmiCecLocalDeviceAudioSystem.init();
- mAction = new ArcTerminationActionFromAvr(mHdmiCecLocalDeviceAudioSystem);
+ mCallback = new TestCallback();
+ mAction = new ArcTerminationActionFromAvr(mHdmiCecLocalDeviceAudioSystem,
+ mCallback);
mLocalDevices.add(mHdmiCecLocalDeviceAudioSystem);
hdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
@@ -121,6 +126,20 @@ public class ArcTerminationActionFromAvrTest {
mTestLooper.dispatchAll();
}
+ private static class TestCallback extends IHdmiControlCallback.Stub {
+ private final ArrayList<Integer> mCallbackResult = new ArrayList<Integer>();
+
+ @Override
+ public void onComplete(int result) {
+ mCallbackResult.add(result);
+ }
+
+ private int getResult() {
+ assertThat(mCallbackResult.size()).isEqualTo(1);
+ return mCallbackResult.get(0);
+ }
+ }
+
@Test
public void testSendMessage_sendFailed() {
mNativeWrapper.setMessageSendResult(Constants.MESSAGE_TERMINATE_ARC,
@@ -133,6 +152,7 @@ public class ArcTerminationActionFromAvrTest {
assertThat(mNativeWrapper.getResultMessages()).contains(terminateArc);
assertThat(mHdmiCecLocalDeviceAudioSystem.isArcEnabled()).isFalse();
+ assertThat(mCallback.getResult()).isEqualTo(HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
}
@Test
@@ -149,6 +169,7 @@ public class ArcTerminationActionFromAvrTest {
mTestLooper.dispatchAll();
assertThat(mHdmiCecLocalDeviceAudioSystem.isArcEnabled()).isFalse();
+ assertThat(mCallback.getResult()).isEqualTo(HdmiControlManager.RESULT_TIMEOUT);
}
@Test
@@ -167,5 +188,28 @@ public class ArcTerminationActionFromAvrTest {
mTestLooper.dispatchAll();
assertThat(mHdmiCecLocalDeviceAudioSystem.isArcEnabled()).isFalse();
+ assertThat(mCallback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS);
+ }
+
+ @Test
+ public void testReportArcTerminated_featureAbort() {
+ mHdmiCecLocalDeviceAudioSystem.addAndStartAction(mAction);
+ mTestLooper.dispatchAll();
+ HdmiCecMessage terminateArc = HdmiCecMessageBuilder.buildTerminateArc(
+ Constants.ADDR_AUDIO_SYSTEM, Constants.ADDR_TV);
+
+ assertThat(mNativeWrapper.getResultMessages()).contains(terminateArc);
+
+ HdmiCecMessage arcTerminatedResponse = HdmiCecMessageBuilder.buildFeatureAbortCommand(
+ Constants.ADDR_TV,
+ Constants.ADDR_AUDIO_SYSTEM,
+ Constants.MESSAGE_TERMINATE_ARC,
+ Constants.ABORT_REFUSED);
+
+ mNativeWrapper.onCecMessage(arcTerminatedResponse);
+ mTestLooper.dispatchAll();
+
+ assertThat(mHdmiCecLocalDeviceAudioSystem.isArcEnabled()).isFalse();
+ assertThat(mCallback.getResult()).isEqualTo(HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
}
}
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 7c6c990500f3..de2c2181d3ab 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
@@ -535,6 +535,25 @@ public class HdmiCecLocalDeviceAudioSystemTest {
}
@Test
+ public void handleRequestArcTerminate_callbackIsPreserved() throws Exception {
+ TestCallback callback = new TestCallback();
+
+ mHdmiCecLocalDeviceAudioSystem.setArcStatus(true);
+ assertThat(mHdmiCecLocalDeviceAudioSystem.isArcEnabled()).isTrue();
+ mHdmiCecLocalDeviceAudioSystem.addAndStartAction(
+ new ArcTerminationActionFromAvr(mHdmiCecLocalDeviceAudioSystem, callback));
+
+ HdmiCecMessage message =
+ HdmiCecMessageBuilder.buildRequestArcTermination(ADDR_TV, ADDR_AUDIO_SYSTEM);
+ assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcTermination(message))
+ .isEqualTo(Constants.HANDLED);
+
+ mTestLooper.dispatchAll();
+ assertThat(mHdmiCecLocalDeviceAudioSystem.getActions(
+ ArcTerminationActionFromAvr.class).get(0).mCallbacks.get(0)).isEqualTo(callback);
+ }
+
+ @Test
public void handleRequestArcInit_arcIsNotSupported() throws Exception {
HdmiCecMessage message =
HdmiCecMessageBuilder.buildRequestArcInitiation(ADDR_TV, ADDR_AUDIO_SYSTEM);
@@ -880,4 +899,13 @@ public class HdmiCecLocalDeviceAudioSystemTest {
assertThat(mNativeWrapper.getResultMessages()).doesNotContain(
systemAudioModeRequest_fromAudioSystem);
}
+
+ private static class TestCallback extends IHdmiControlCallback.Stub {
+ private final ArrayList<Integer> mCallbackResult = new ArrayList<Integer>();
+
+ @Override
+ public void onComplete(int result) {
+ mCallbackResult.add(result);
+ }
+ }
}
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 ef2b21211297..49a0a9a52c47 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
@@ -1036,6 +1036,7 @@ public class HdmiControlServiceTest {
@Test
public void setSoundbarMode_enabled_addAudioSystemLocalDevice() {
+ mHdmiControlServiceSpy.setPowerStatus(HdmiControlManager.POWER_STATUS_ON);
// Initialize the local devices excluding the audio system.
mHdmiControlServiceSpy.clearCecLocalDevices();
mLocalDevices.remove(mAudioSystemDeviceSpy);
@@ -1053,6 +1054,7 @@ public class HdmiControlServiceTest {
@Test
public void setSoundbarMode_disabled_removeAudioSystemLocalDevice() {
+ mHdmiControlServiceSpy.setPowerStatus(HdmiControlManager.POWER_STATUS_ON);
// Initialize the local devices excluding the audio system.
mHdmiControlServiceSpy.clearCecLocalDevices();
mLocalDevices.remove(mAudioSystemDeviceSpy);
@@ -1073,6 +1075,10 @@ public class HdmiControlServiceTest {
HdmiControlManager.CEC_SETTING_NAME_SOUNDBAR_MODE,
HdmiControlManager.SOUNDBAR_MODE_DISABLED);
mTestLooper.dispatchAll();
+
+ // Wait for ArcTerminationActionFromAvr timeout for the logical address allocation to start.
+ mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
+ mTestLooper.dispatchAll();
assertThat(mHdmiControlServiceSpy.audioSystem()).isNull();
}
diff --git a/services/tests/servicestests/src/com/android/server/input/KeyRemapperTests.kt b/services/tests/servicestests/src/com/android/server/input/KeyRemapperTests.kt
new file mode 100644
index 000000000000..c22782c91bdf
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/input/KeyRemapperTests.kt
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.input
+
+import android.content.Context
+import android.content.ContextWrapper
+import android.hardware.input.IInputManager
+import android.hardware.input.InputManager
+import android.os.test.TestLooper
+import android.platform.test.annotations.Presubmit
+import android.view.InputDevice
+import android.view.KeyEvent
+import androidx.test.core.app.ApplicationProvider
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.junit.MockitoJUnit
+import java.io.FileNotFoundException
+import java.io.FileOutputStream
+import java.io.IOException
+import java.io.InputStream
+
+private fun createKeyboard(deviceId: Int): InputDevice =
+ InputDevice.Builder()
+ .setId(deviceId)
+ .setName("Device $deviceId")
+ .setDescriptor("descriptor $deviceId")
+ .setSources(InputDevice.SOURCE_KEYBOARD)
+ .setKeyboardType(InputDevice.KEYBOARD_TYPE_ALPHABETIC)
+ .setExternal(true)
+ .build()
+
+/**
+ * Tests for {@link KeyRemapper}.
+ *
+ * Build/Install/Run:
+ * atest FrameworksServicesTests:KeyRemapperTests
+ */
+@Presubmit
+class KeyRemapperTests {
+
+ companion object {
+ const val DEVICE_ID = 1
+ val REMAPPABLE_KEYS = intArrayOf(
+ KeyEvent.KEYCODE_CTRL_LEFT, KeyEvent.KEYCODE_CTRL_RIGHT,
+ KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_META_RIGHT,
+ KeyEvent.KEYCODE_ALT_LEFT, KeyEvent.KEYCODE_ALT_RIGHT,
+ KeyEvent.KEYCODE_SHIFT_LEFT, KeyEvent.KEYCODE_SHIFT_RIGHT,
+ KeyEvent.KEYCODE_CAPS_LOCK
+ )
+ }
+
+ @get:Rule
+ val rule = MockitoJUnit.rule()!!
+
+ @Mock
+ private lateinit var iInputManager: IInputManager
+ @Mock
+ private lateinit var native: NativeInputManagerService
+ private lateinit var mKeyRemapper: KeyRemapper
+ private lateinit var context: Context
+ private lateinit var dataStore: PersistentDataStore
+ private lateinit var testLooper: TestLooper
+
+ @Before
+ fun setup() {
+ context = Mockito.spy(ContextWrapper(ApplicationProvider.getApplicationContext()))
+ dataStore = PersistentDataStore(object : PersistentDataStore.Injector() {
+ override fun openRead(): InputStream? {
+ throw FileNotFoundException()
+ }
+
+ override fun startWrite(): FileOutputStream? {
+ throw IOException()
+ }
+
+ override fun finishWrite(fos: FileOutputStream?, success: Boolean) {}
+ })
+ testLooper = TestLooper()
+ mKeyRemapper = KeyRemapper(
+ context,
+ native,
+ dataStore,
+ testLooper.looper
+ )
+ val inputManager = InputManager.resetInstance(iInputManager)
+ Mockito.`when`(context.getSystemService(Mockito.eq(Context.INPUT_SERVICE)))
+ .thenReturn(inputManager)
+ Mockito.`when`(iInputManager.inputDeviceIds).thenReturn(intArrayOf(DEVICE_ID))
+ }
+
+ @After
+ fun tearDown() {
+ InputManager.clearInstance()
+ }
+
+ @Test
+ fun testKeyRemapping() {
+ val keyboard = createKeyboard(DEVICE_ID)
+ Mockito.`when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboard)
+
+ for (i in REMAPPABLE_KEYS.indices) {
+ val fromKeyCode = REMAPPABLE_KEYS[i]
+ val toKeyCode = REMAPPABLE_KEYS[(i + 1) % REMAPPABLE_KEYS.size]
+ mKeyRemapper.remapKey(fromKeyCode, toKeyCode)
+ testLooper.dispatchNext()
+ }
+
+ val remapping = mKeyRemapper.keyRemapping
+ val expectedSize = REMAPPABLE_KEYS.size
+ assertEquals("Remapping size should be $expectedSize", expectedSize, remapping.size)
+
+ for (i in REMAPPABLE_KEYS.indices) {
+ val fromKeyCode = REMAPPABLE_KEYS[i]
+ val toKeyCode = REMAPPABLE_KEYS[(i + 1) % REMAPPABLE_KEYS.size]
+ assertEquals(
+ "Remapping should include mapping from $fromKeyCode to $toKeyCode",
+ toKeyCode,
+ remapping.getOrDefault(fromKeyCode, -1)
+ )
+ }
+
+ mKeyRemapper.clearAllKeyRemappings()
+ testLooper.dispatchNext()
+
+ assertEquals(
+ "Remapping size should be 0 after clearAllModifierKeyRemappings",
+ 0,
+ mKeyRemapper.keyRemapping.size
+ )
+ }
+} \ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
index dc47b5eaea0e..0589b3a91225 100644
--- a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
@@ -8,6 +8,7 @@ import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -22,6 +23,7 @@ import android.os.Build;
import android.os.PersistableBundle;
import android.os.SystemClock;
import android.test.RenamingDelegatingContext;
+import android.util.ArraySet;
import android.util.Log;
import android.util.Pair;
@@ -38,9 +40,9 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.io.File;
import java.time.Clock;
import java.time.ZoneOffset;
-import java.util.Iterator;
/**
* Test reading and writing correctly from file.
@@ -93,11 +95,147 @@ public class JobStoreTest {
mTaskStoreUnderTest.waitForWriteToCompleteForTesting(5_000L);
}
+ private void setUseSplitFiles(boolean useSplitFiles) throws Exception {
+ mTaskStoreUnderTest.setUseSplitFiles(useSplitFiles);
+ waitForPendingIo();
+ }
+
private void waitForPendingIo() throws Exception {
assertTrue("Timed out waiting for persistence I/O to complete",
mTaskStoreUnderTest.waitForWriteToCompleteForTesting(5_000L));
}
+ /** Test that we properly remove the last job of an app from the persisted file. */
+ @Test
+ public void testRemovingLastJob_singleFile() throws Exception {
+ setUseSplitFiles(false);
+ runRemovingLastJob();
+ }
+
+ /** Test that we properly remove the last job of an app from the persisted file. */
+ @Test
+ public void testRemovingLastJob_splitFiles() throws Exception {
+ setUseSplitFiles(true);
+ runRemovingLastJob();
+ }
+
+ private void runRemovingLastJob() throws Exception {
+ final JobInfo task1 = new Builder(8, mComponent)
+ .setRequiresDeviceIdle(true)
+ .setPeriodic(10000L)
+ .setRequiresCharging(true)
+ .setPersisted(true)
+ .build();
+ final JobInfo task2 = new Builder(12, mComponent)
+ .setMinimumLatency(5000L)
+ .setBackoffCriteria(15000L, JobInfo.BACKOFF_POLICY_LINEAR)
+ .setOverrideDeadline(30000L)
+ .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
+ .setPersisted(true)
+ .build();
+ final int uid1 = SOME_UID;
+ final int uid2 = uid1 + 1;
+ final JobStatus JobStatus1 = JobStatus.createFromJobInfo(task1, uid1, null, -1, null);
+ final JobStatus JobStatus2 = JobStatus.createFromJobInfo(task2, uid2, null, -1, null);
+ runWritingJobsToDisk(JobStatus1, JobStatus2);
+
+ // Remove 1 job
+ mTaskStoreUnderTest.remove(JobStatus1, true);
+ waitForPendingIo();
+ JobSet jobStatusSet = new JobSet();
+ mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true);
+ assertEquals("Incorrect # of persisted tasks.", 1, jobStatusSet.size());
+ JobStatus loaded = jobStatusSet.getAllJobs().iterator().next();
+
+ assertJobsEqual(JobStatus2, loaded);
+ assertTrue("JobStore#contains invalid.", mTaskStoreUnderTest.containsJob(JobStatus2));
+
+ // Remove 2nd job
+ mTaskStoreUnderTest.remove(JobStatus2, true);
+ waitForPendingIo();
+ jobStatusSet = new JobSet();
+ mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true);
+ assertEquals("Incorrect # of persisted tasks.", 0, jobStatusSet.size());
+ }
+
+ /** Test that we properly clear the persisted file when all jobs are dropped. */
+ @Test
+ public void testClearJobs_singleFile() throws Exception {
+ setUseSplitFiles(false);
+ runClearJobs();
+ }
+
+ /** Test that we properly clear the persisted file when all jobs are dropped. */
+ @Test
+ public void testClearJobs_splitFiles() throws Exception {
+ setUseSplitFiles(true);
+ runClearJobs();
+ }
+
+ private void runClearJobs() throws Exception {
+ final JobInfo task1 = new Builder(8, mComponent)
+ .setRequiresDeviceIdle(true)
+ .setPeriodic(10000L)
+ .setRequiresCharging(true)
+ .setPersisted(true)
+ .build();
+ final JobInfo task2 = new Builder(12, mComponent)
+ .setMinimumLatency(5000L)
+ .setBackoffCriteria(15000L, JobInfo.BACKOFF_POLICY_LINEAR)
+ .setOverrideDeadline(30000L)
+ .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
+ .setPersisted(true)
+ .build();
+ final int uid1 = SOME_UID;
+ final int uid2 = uid1 + 1;
+ final JobStatus JobStatus1 = JobStatus.createFromJobInfo(task1, uid1, null, -1, null);
+ final JobStatus JobStatus2 = JobStatus.createFromJobInfo(task2, uid2, null, -1, null);
+ runWritingJobsToDisk(JobStatus1, JobStatus2);
+
+ // Remove all jobs
+ mTaskStoreUnderTest.clear();
+ waitForPendingIo();
+ JobSet jobStatusSet = new JobSet();
+ mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true);
+ assertEquals("Incorrect # of persisted tasks.", 0, jobStatusSet.size());
+ }
+
+ @Test
+ public void testExtractUidFromJobFileName() {
+ File file = new File(mTestContext.getFilesDir(), "randomName");
+ assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file));
+
+ file = new File(mTestContext.getFilesDir(), "jobs.xml");
+ assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file));
+
+ file = new File(mTestContext.getFilesDir(), ".xml");
+ assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file));
+
+ file = new File(mTestContext.getFilesDir(), "1000.xml");
+ assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file));
+
+ file = new File(mTestContext.getFilesDir(), "10000");
+ assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file));
+
+ file = new File(mTestContext.getFilesDir(), JobStore.JOB_FILE_SPLIT_PREFIX);
+ assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file));
+
+ file = new File(mTestContext.getFilesDir(), JobStore.JOB_FILE_SPLIT_PREFIX + "text.xml");
+ assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file));
+
+ file = new File(mTestContext.getFilesDir(), JobStore.JOB_FILE_SPLIT_PREFIX + ".xml");
+ assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file));
+
+ file = new File(mTestContext.getFilesDir(), JobStore.JOB_FILE_SPLIT_PREFIX + "-10123.xml");
+ assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file));
+
+ file = new File(mTestContext.getFilesDir(), JobStore.JOB_FILE_SPLIT_PREFIX + "1.xml");
+ assertEquals(1, JobStore.extractUidFromJobFileName(file));
+
+ file = new File(mTestContext.getFilesDir(), JobStore.JOB_FILE_SPLIT_PREFIX + "101023.xml");
+ assertEquals(101023, JobStore.extractUidFromJobFileName(file));
+ }
+
@Test
public void testStringToIntArrayAndIntArrayToString() {
final int[] netCapabilitiesIntArray = { 1, 3, 5, 7, 9 };
@@ -144,13 +282,13 @@ public class JobStoreTest {
@Test
public void testWritingTwoJobsToDisk_singleFile() throws Exception {
- mTaskStoreUnderTest.setUseSplitFiles(false);
+ setUseSplitFiles(false);
runWritingTwoJobsToDisk();
}
@Test
public void testWritingTwoJobsToDisk_splitFiles() throws Exception {
- mTaskStoreUnderTest.setUseSplitFiles(true);
+ setUseSplitFiles(true);
runWritingTwoJobsToDisk();
}
@@ -172,28 +310,44 @@ public class JobStoreTest {
final int uid2 = uid1 + 1;
final JobStatus taskStatus1 = JobStatus.createFromJobInfo(task1, uid1, null, -1, null);
final JobStatus taskStatus2 = JobStatus.createFromJobInfo(task2, uid2, null, -1, null);
- mTaskStoreUnderTest.add(taskStatus1);
- mTaskStoreUnderTest.add(taskStatus2);
+
+ runWritingJobsToDisk(taskStatus1, taskStatus2);
+ }
+
+ private void runWritingJobsToDisk(JobStatus... jobStatuses) throws Exception {
+ ArraySet<JobStatus> expectedJobs = new ArraySet<>();
+ for (JobStatus jobStatus : jobStatuses) {
+ mTaskStoreUnderTest.add(jobStatus);
+ expectedJobs.add(jobStatus);
+ }
waitForPendingIo();
final JobSet jobStatusSet = new JobSet();
mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true);
- assertEquals("Incorrect # of persisted tasks.", 2, jobStatusSet.size());
- Iterator<JobStatus> it = jobStatusSet.getAllJobs().iterator();
- JobStatus loaded1 = it.next();
- JobStatus loaded2 = it.next();
-
- // Reverse them so we know which comparison to make.
- if (loaded1.getJobId() != 8) {
- JobStatus tmp = loaded1;
- loaded1 = loaded2;
- loaded2 = tmp;
+ assertEquals("Incorrect # of persisted tasks.", expectedJobs.size(), jobStatusSet.size());
+ int count = 0;
+ final int expectedCount = expectedJobs.size();
+ for (JobStatus loaded : jobStatusSet.getAllJobs()) {
+ count++;
+ for (int i = 0; i < expectedJobs.size(); ++i) {
+ JobStatus expected = expectedJobs.valueAt(i);
+
+ try {
+ assertJobsEqual(expected, loaded);
+ expectedJobs.remove(expected);
+ break;
+ } catch (AssertionError e) {
+ // Not equal. Move along.
+ }
+ }
+ }
+ assertEquals("Loaded more jobs than expected", expectedCount, count);
+ if (expectedJobs.size() > 0) {
+ fail("Not all expected jobs were restored");
+ }
+ for (JobStatus jobStatus : jobStatuses) {
+ assertTrue("JobStore#contains invalid.", mTaskStoreUnderTest.containsJob(jobStatus));
}
-
- assertJobsEqual(taskStatus1, loaded1);
- assertJobsEqual(taskStatus2, loaded2);
- assertTrue("JobStore#contains invalid.", mTaskStoreUnderTest.containsJob(taskStatus1));
- assertTrue("JobStore#contains invalid.", mTaskStoreUnderTest.containsJob(taskStatus2));
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
index c934e653564e..4668e5d53c78 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
@@ -16,6 +16,9 @@
package com.android.server.locksettings;
+import static android.app.admin.DevicePolicyManager.DEPRECATE_USERMANAGERINTERNAL_DEVICEPOLICY_FLAG;
+import static android.provider.DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER;
+
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
@@ -33,22 +36,28 @@ import android.app.admin.DevicePolicyManagerInternal;
import android.app.admin.DeviceStateCache;
import android.app.trust.TrustManager;
import android.content.ComponentName;
+import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
-import android.hardware.authsecret.V1_0.IAuthSecret;
+import android.hardware.authsecret.IAuthSecret;
import android.hardware.face.FaceManager;
import android.hardware.fingerprint.FingerprintManager;
import android.os.FileUtils;
import android.os.IProgressListener;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.os.UserManager;
import android.os.storage.IStorageManager;
import android.os.storage.StorageManager;
+import android.provider.Settings;
+import android.provider.DeviceConfig;
import android.security.KeyStore;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.internal.util.test.FakeSettingsProviderRule;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockSettingsInternal;
import com.android.internal.widget.LockscreenCredential;
@@ -59,6 +68,7 @@ import com.android.server.wm.WindowManagerInternal;
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.runner.RunWith;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
@@ -106,7 +116,8 @@ public abstract class BaseLockSettingsServiceTests {
FingerprintManager mFingerprintManager;
FaceManager mFaceManager;
PackageManager mPackageManager;
- FakeSettings mSettings;
+ @Rule
+ public FakeSettingsProviderRule mSettingsRule = FakeSettingsProvider.rule();
@Before
public void setUp_baseServices() throws Exception {
@@ -126,7 +137,6 @@ public abstract class BaseLockSettingsServiceTests {
mFingerprintManager = mock(FingerprintManager.class);
mFaceManager = mock(FaceManager.class);
mPackageManager = mock(PackageManager.class);
- mSettings = new FakeSettings();
LocalServices.removeServiceForTest(LockSettingsInternal.class);
LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
@@ -134,12 +144,13 @@ public abstract class BaseLockSettingsServiceTests {
LocalServices.addService(DevicePolicyManagerInternal.class, mDevicePolicyManagerInternal);
LocalServices.addService(WindowManagerInternal.class, mMockWindowManager);
- mContext = new MockLockSettingsContext(InstrumentationRegistry.getContext(), mUserManager,
- mNotificationManager, mDevicePolicyManager, mock(StorageManager.class),
- mock(TrustManager.class), mock(KeyguardManager.class), mFingerprintManager,
- mFaceManager, mPackageManager);
+ final Context origContext = InstrumentationRegistry.getContext();
+ mContext = new MockLockSettingsContext(origContext,
+ mSettingsRule.mockContentResolver(origContext), mUserManager, mNotificationManager,
+ mDevicePolicyManager, mock(StorageManager.class), mock(TrustManager.class),
+ mock(KeyguardManager.class), mFingerprintManager, mFaceManager, mPackageManager);
mStorage = new LockSettingsStorageTestable(mContext,
- new File(InstrumentationRegistry.getContext().getFilesDir(), "locksettings"));
+ new File(origContext.getFilesDir(), "locksettings"));
File storageDir = mStorage.mStorageDir;
if (storageDir.exists()) {
FileUtils.deleteContents(storageDir);
@@ -153,7 +164,7 @@ public abstract class BaseLockSettingsServiceTests {
mService = new LockSettingsServiceTestable(mContext, mStorage,
mGateKeeperService, mKeyStore, setUpStorageManagerMock(), mActivityManager,
mSpManager, mAuthSecretService, mGsiService, mRecoverableKeyStoreManager,
- mUserManagerInternal, mDeviceStateCache, mSettings);
+ mUserManagerInternal, mDeviceStateCache);
mService.mHasSecureLockScreen = true;
when(mUserManager.getUserInfo(eq(PRIMARY_USER_ID))).thenReturn(PRIMARY_USER_INFO);
mPrimaryUserProfiles.add(PRIMARY_USER_INFO);
@@ -181,15 +192,34 @@ public abstract class BaseLockSettingsServiceTests {
// Adding a fake Device Owner app which will enable escrow token support in LSS.
when(mDevicePolicyManager.getDeviceOwnerComponentOnAnyUser()).thenReturn(
new ComponentName("com.dummy.package", ".FakeDeviceOwner"));
+ // TODO(b/258213147): Remove
+ DeviceConfig.setProperty(NAMESPACE_DEVICE_POLICY_MANAGER,
+ DEPRECATE_USERMANAGERINTERNAL_DEVICEPOLICY_FLAG, "true", /* makeDefault= */ false);
when(mUserManagerInternal.isDeviceManaged()).thenReturn(true);
+ when(mDeviceStateCache.isUserOrganizationManaged(anyInt())).thenReturn(true);
when(mDeviceStateCache.isDeviceProvisioned()).thenReturn(true);
mockBiometricsHardwareFingerprintsAndTemplates(PRIMARY_USER_ID);
mockBiometricsHardwareFingerprintsAndTemplates(MANAGED_PROFILE_USER_ID);
- mSettings.setDeviceProvisioned(true);
+ setDeviceProvisioned(true);
mLocalService = LocalServices.getService(LockSettingsInternal.class);
}
+ protected void setDeviceProvisioned(boolean provisioned) {
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Settings.Global.DEVICE_PROVISIONED, provisioned ? 1 : 0);
+ }
+
+ protected void setUserSetupComplete(boolean complete) {
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
+ Settings.Secure.USER_SETUP_COMPLETE, complete ? 1 : 0, UserHandle.USER_SYSTEM);
+ }
+
+ protected void setSecureFrpMode(boolean secure) {
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
+ Settings.Secure.SECURE_FRP_MODE, secure ? 1 : 0, UserHandle.USER_SYSTEM);
+ }
+
private UserInfo installChildProfile(int profileId) {
final UserInfo userInfo = new UserInfo(
profileId, null, null, UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_MANAGED_PROFILE);
@@ -199,7 +229,10 @@ public abstract class BaseLockSettingsServiceTests {
when(mUserManager.getProfileParent(eq(profileId))).thenReturn(PRIMARY_USER_INFO);
when(mUserManager.isUserRunning(eq(profileId))).thenReturn(true);
when(mUserManager.isUserUnlocked(eq(profileId))).thenReturn(true);
+ // TODO(b/258213147): Remove
when(mUserManagerInternal.isUserManaged(eq(profileId))).thenReturn(true);
+ when(mDeviceStateCache.isUserOrganizationManaged(eq(profileId)))
+ .thenReturn(true);
return userInfo;
}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/FakeSettings.java b/services/tests/servicestests/src/com/android/server/locksettings/FakeSettings.java
deleted file mode 100644
index 2bcd653a5476..000000000000
--- a/services/tests/servicestests/src/com/android/server/locksettings/FakeSettings.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.server.locksettings;
-
-import android.content.ContentResolver;
-import android.os.UserHandle;
-import android.provider.Settings;
-
-public class FakeSettings {
-
- private int mDeviceProvisioned;
- private int mSecureFrpMode;
- private int mUserSetupComplete;
-
- public void setDeviceProvisioned(boolean provisioned) {
- mDeviceProvisioned = provisioned ? 1 : 0;
- }
-
- public void setSecureFrpMode(boolean secure) {
- mSecureFrpMode = secure ? 1 : 0;
- }
-
- public void setUserSetupComplete(boolean complete) {
- mUserSetupComplete = complete ? 1 : 0;
- }
-
- public int globalGetInt(String keyName) {
- switch (keyName) {
- case Settings.Global.DEVICE_PROVISIONED:
- return mDeviceProvisioned;
- default:
- throw new IllegalArgumentException("Unhandled global settings: " + keyName);
- }
- }
-
- public int secureGetInt(ContentResolver contentResolver, String keyName, int defaultValue,
- int userId) {
- if (Settings.Secure.SECURE_FRP_MODE.equals(keyName) && userId == UserHandle.USER_SYSTEM) {
- return mSecureFrpMode;
- }
- if (Settings.Secure.USER_SETUP_COMPLETE.equals(keyName)
- && userId == UserHandle.USER_SYSTEM) {
- return mUserSetupComplete;
- }
- return defaultValue;
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
index 85db23c6b717..d3b647dba5c8 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
@@ -20,10 +20,9 @@ import static org.mockito.Mockito.mock;
import android.app.IActivityManager;
import android.app.admin.DeviceStateCache;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.pm.UserInfo;
-import android.hardware.authsecret.V1_0.IAuthSecret;
+import android.hardware.authsecret.IAuthSecret;
import android.os.Handler;
import android.os.Parcel;
import android.os.Process;
@@ -52,14 +51,12 @@ public class LockSettingsServiceTestable extends LockSettingsService {
private RecoverableKeyStoreManager mRecoverableKeyStoreManager;
private UserManagerInternal mUserManagerInternal;
private DeviceStateCache mDeviceStateCache;
- private FakeSettings mSettings;
public MockInjector(Context context, LockSettingsStorage storage, KeyStore keyStore,
IActivityManager activityManager,
IStorageManager storageManager, SyntheticPasswordManager spManager,
FakeGsiService gsiService, RecoverableKeyStoreManager recoverableKeyStoreManager,
- UserManagerInternal userManagerInternal, DeviceStateCache deviceStateCache,
- FakeSettings settings) {
+ UserManagerInternal userManagerInternal, DeviceStateCache deviceStateCache) {
super(context);
mLockSettingsStorage = storage;
mKeyStore = keyStore;
@@ -70,7 +67,6 @@ public class LockSettingsServiceTestable extends LockSettingsService {
mRecoverableKeyStoreManager = recoverableKeyStoreManager;
mUserManagerInternal = userManagerInternal;
mDeviceStateCache = deviceStateCache;
- mSettings = settings;
}
@Override
@@ -119,18 +115,6 @@ public class LockSettingsServiceTestable extends LockSettingsService {
}
@Override
- public int settingsGlobalGetInt(ContentResolver contentResolver, String keyName,
- int defaultValue) {
- return mSettings.globalGetInt(keyName);
- }
-
- @Override
- public int settingsSecureGetInt(ContentResolver contentResolver, String keyName,
- int defaultValue, int userId) {
- return mSettings.secureGetInt(contentResolver, keyName, defaultValue, userId);
- }
-
- @Override
public UserManagerInternal getUserManagerInternal() {
return mUserManagerInternal;
}
@@ -165,13 +149,12 @@ public class LockSettingsServiceTestable extends LockSettingsService {
IStorageManager storageManager, IActivityManager mActivityManager,
SyntheticPasswordManager spManager, IAuthSecret authSecretService,
FakeGsiService gsiService, RecoverableKeyStoreManager recoverableKeyStoreManager,
- UserManagerInternal userManagerInternal, DeviceStateCache deviceStateCache,
- FakeSettings settings) {
+ UserManagerInternal userManagerInternal, DeviceStateCache deviceStateCache) {
super(new MockInjector(context, storage, keystore, mActivityManager,
- storageManager, spManager, gsiService,
- recoverableKeyStoreManager, userManagerInternal, deviceStateCache, settings));
+ storageManager, spManager, gsiService, recoverableKeyStoreManager,
+ userManagerInternal, deviceStateCache));
mGateKeeperService = gatekeeper;
- mAuthSecretService = authSecretService;
+ mAuthSecretServiceAidl = authSecretService;
}
@Override
@@ -216,4 +199,4 @@ public class LockSettingsServiceTestable extends LockSettingsService {
UserInfo userInfo = mUserManager.getUserInfo(userId);
return userInfo.isCloneProfile() || userInfo.isManagedProfile();
}
-} \ No newline at end of file
+}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
index 3f259e343ee6..196226a220a7 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
@@ -424,8 +424,8 @@ public class LockSettingsServiceTests extends BaseLockSettingsServiceTests {
@Test
public void testCredentialChangeNotPossibleInSecureFrpModeDuringSuw() {
- mSettings.setUserSetupComplete(false);
- mSettings.setSecureFrpMode(true);
+ setUserSetupComplete(false);
+ setSecureFrpMode(true);
try {
mService.setLockCredential(newPassword("1234"), nonePassword(), PRIMARY_USER_ID);
fail("Password shouldn't be changeable before FRP unlock");
@@ -434,8 +434,8 @@ public class LockSettingsServiceTests extends BaseLockSettingsServiceTests {
@Test
public void testCredentialChangePossibleInSecureFrpModeAfterSuw() {
- mSettings.setUserSetupComplete(true);
- mSettings.setSecureFrpMode(true);
+ setUserSetupComplete(true);
+ setSecureFrpMode(true);
assertTrue(mService.setLockCredential(newPassword("1234"), nonePassword(),
PRIMARY_USER_ID));
}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
index a6638587bf22..95d0e15bb2a8 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
@@ -31,6 +31,7 @@ import android.app.KeyguardManager;
import android.app.NotificationManager;
import android.app.admin.DevicePolicyManager;
import android.app.trust.TrustManager;
+import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.database.sqlite.SQLiteDatabase;
@@ -49,11 +50,14 @@ import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.internal.util.test.FakeSettingsProviderRule;
import com.android.server.PersistentDataBlockManagerInternal;
import com.android.server.locksettings.LockSettingsStorage.PersistentData;
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -78,14 +82,17 @@ public class LockSettingsStorageTests {
public static final byte[] PAYLOAD = new byte[] {1, 2, -1, -2, 33};
- LockSettingsStorageTestable mStorage;
- File mStorageDir;
-
+ private LockSettingsStorageTestable mStorage;
+ private File mStorageDir;
private File mDb;
+ @Rule
+ public FakeSettingsProviderRule mSettingsRule = FakeSettingsProvider.rule();
@Before
public void setUp() throws Exception {
- mStorageDir = new File(InstrumentationRegistry.getContext().getFilesDir(), "locksettings");
+ final Context origContext = InstrumentationRegistry.getContext();
+
+ mStorageDir = new File(origContext.getFilesDir(), "locksettings");
mDb = InstrumentationRegistry.getContext().getDatabasePath("locksettings.db");
assertTrue(mStorageDir.exists() || mStorageDir.mkdirs());
@@ -98,8 +105,8 @@ public class LockSettingsStorageTests {
// User 3 is a profile of user 0.
when(mockUserManager.getProfileParent(eq(3))).thenReturn(new UserInfo(0, "name", 0));
- MockLockSettingsContext context = new MockLockSettingsContext(
- InstrumentationRegistry.getContext(), mockUserManager,
+ MockLockSettingsContext context = new MockLockSettingsContext(origContext,
+ mSettingsRule.mockContentResolver(origContext), mockUserManager,
mock(NotificationManager.class), mock(DevicePolicyManager.class),
mock(StorageManager.class), mock(TrustManager.class), mock(KeyguardManager.class),
mock(FingerprintManager.class), mock(FaceManager.class),
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockscreenFrpTest.java b/services/tests/servicestests/src/com/android/server/locksettings/LockscreenFrpTest.java
index c2f94e202ca3..fc0ca7eda243 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockscreenFrpTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockscreenFrpTest.java
@@ -44,7 +44,7 @@ public class LockscreenFrpTest extends BaseLockSettingsServiceTests {
@Before
public void setDeviceNotProvisioned() throws Exception {
// FRP credential can only be verified prior to provisioning
- mSettings.setDeviceProvisioned(false);
+ setDeviceProvisioned(false);
}
@Before
@@ -98,6 +98,7 @@ public class LockscreenFrpTest extends BaseLockSettingsServiceTests {
mService.setLockCredential(newPassword("1234"), nonePassword(), PRIMARY_USER_ID);
assertEquals(CREDENTIAL_TYPE_PASSWORD, mService.getCredentialType(USER_FRP));
+ setDeviceProvisioned(true);
mService.setLockCredential(nonePassword(), newPassword("1234"), PRIMARY_USER_ID);
assertEquals(CREDENTIAL_TYPE_NONE, mService.getCredentialType(USER_FRP));
}
@@ -106,7 +107,7 @@ public class LockscreenFrpTest extends BaseLockSettingsServiceTests {
public void testFrpCredential_cannotVerifyAfterProvsioning() {
mService.setLockCredential(newPin("1234"), nonePassword(), PRIMARY_USER_ID);
- mSettings.setDeviceProvisioned(true);
+ setDeviceProvisioned(true);
assertEquals(VerifyCredentialResponse.RESPONSE_ERROR,
mService.verifyCredential(newPin("1234"), USER_FRP, 0 /* flags */)
.getResponseCode());
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/MockLockSettingsContext.java b/services/tests/servicestests/src/com/android/server/locksettings/MockLockSettingsContext.java
index efa1b044f8f9..21c367b3a6e2 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/MockLockSettingsContext.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/MockLockSettingsContext.java
@@ -21,6 +21,7 @@ import android.app.NotificationManager;
import android.app.admin.DevicePolicyManager;
import android.app.trust.TrustManager;
import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
@@ -35,22 +36,25 @@ import android.os.storage.StorageManager;
public class MockLockSettingsContext extends ContextWrapper {
- private UserManager mUserManager;
- private NotificationManager mNotificationManager;
- private DevicePolicyManager mDevicePolicyManager;
- private StorageManager mStorageManager;
- private TrustManager mTrustManager;
- private KeyguardManager mKeyguardManager;
- private FingerprintManager mFingerprintManager;
- private FaceManager mFaceManager;
- private PackageManager mPackageManager;
+ private final ContentResolver mContentResolver;
+ private final UserManager mUserManager;
+ private final NotificationManager mNotificationManager;
+ private final DevicePolicyManager mDevicePolicyManager;
+ private final StorageManager mStorageManager;
+ private final TrustManager mTrustManager;
+ private final KeyguardManager mKeyguardManager;
+ private final FingerprintManager mFingerprintManager;
+ private final FaceManager mFaceManager;
+ private final PackageManager mPackageManager;
- public MockLockSettingsContext(Context base, UserManager userManager,
- NotificationManager notificationManager, DevicePolicyManager devicePolicyManager,
- StorageManager storageManager, TrustManager trustManager,
- KeyguardManager keyguardManager, FingerprintManager fingerprintManager,
- FaceManager faceManager, PackageManager packageManager) {
+ public MockLockSettingsContext(Context base, ContentResolver contentResolver,
+ UserManager userManager, NotificationManager notificationManager,
+ DevicePolicyManager devicePolicyManager, StorageManager storageManager,
+ TrustManager trustManager, KeyguardManager keyguardManager,
+ FingerprintManager fingerprintManager, FaceManager faceManager,
+ PackageManager packageManager) {
super(base);
+ mContentResolver = contentResolver;
mUserManager = userManager;
mNotificationManager = notificationManager;
mDevicePolicyManager = devicePolicyManager;
@@ -63,6 +67,11 @@ public class MockLockSettingsContext extends ContextWrapper {
}
@Override
+ public ContentResolver getContentResolver() {
+ return mContentResolver;
+ }
+
+ @Override
public Object getSystemService(String name) {
if (USER_SERVICE.equals(name)) {
return mUserManager;
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 3f4bec653091..b00467cf8637 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
@@ -28,6 +28,7 @@ import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.atLeastOnce;
@@ -36,8 +37,8 @@ import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.app.admin.PasswordMetrics;
import android.app.PropertyInvalidatedCache;
+import android.app.admin.PasswordMetrics;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
@@ -59,6 +60,7 @@ import org.mockito.ArgumentCaptor;
import java.io.File;
import java.util.ArrayList;
+import java.util.Arrays;
/**
@@ -136,6 +138,43 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
assertTrue(mService.isSyntheticPasswordBasedCredential(userId));
}
+ protected void initializeSyntheticPassword(int userId) {
+ synchronized (mService.mSpManager) {
+ mService.initializeSyntheticPasswordLocked(userId);
+ }
+ }
+
+ // Tests that the FRP credential is updated when an LSKF-based protector is created for the user
+ // that owns the FRP credential, if the device is already provisioned.
+ @Test
+ public void testFrpCredentialSyncedIfDeviceProvisioned() throws RemoteException {
+ setDeviceProvisioned(true);
+ initializeSyntheticPassword(PRIMARY_USER_ID);
+ verify(mStorage.mPersistentDataBlockManager).setFrpCredentialHandle(any());
+ }
+
+ // Tests that the FRP credential is not updated when an LSKF-based protector is created for the
+ // user that owns the FRP credential, if the new credential is empty and the device is not yet
+ // provisioned.
+ @Test
+ public void testEmptyFrpCredentialNotSyncedIfDeviceNotProvisioned() throws RemoteException {
+ setDeviceProvisioned(false);
+ initializeSyntheticPassword(PRIMARY_USER_ID);
+ verify(mStorage.mPersistentDataBlockManager, never()).setFrpCredentialHandle(any());
+ }
+
+ // Tests that the FRP credential is updated when an LSKF-based protector is created for the user
+ // that owns the FRP credential, if the new credential is nonempty and the device is not yet
+ // provisioned.
+ @Test
+ public void testNonEmptyFrpCredentialSyncedIfDeviceNotProvisioned() throws RemoteException {
+ setDeviceProvisioned(false);
+ initializeSyntheticPassword(PRIMARY_USER_ID);
+ verify(mStorage.mPersistentDataBlockManager, never()).setFrpCredentialHandle(any());
+ mService.setLockCredential(newPassword("password"), nonePassword(), PRIMARY_USER_ID);
+ verify(mStorage.mPersistentDataBlockManager).setFrpCredentialHandle(any());
+ }
+
@Test
public void testChangeCredential() throws RemoteException {
final LockscreenCredential password = newPassword("password");
@@ -192,9 +231,11 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
badPassword, PRIMARY_USER_ID, 0 /* flags */).getResponseCode());
// Check the same secret was passed each time
- ArgumentCaptor<ArrayList<Byte>> secret = ArgumentCaptor.forClass(ArrayList.class);
- verify(mAuthSecretService, atLeastOnce()).primaryUserCredential(secret.capture());
- assertEquals(1, secret.getAllValues().stream().distinct().count());
+ ArgumentCaptor<byte[]> secret = ArgumentCaptor.forClass(byte[].class);
+ verify(mAuthSecretService, atLeastOnce()).setPrimaryUserCredential(secret.capture());
+ for (byte[] val : secret.getAllValues()) {
+ assertArrayEquals(val, secret.getAllValues().get(0));
+ }
}
@Test
@@ -205,7 +246,7 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
reset(mAuthSecretService);
assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
password, PRIMARY_USER_ID, 0 /* flags */).getResponseCode());
- verify(mAuthSecretService).primaryUserCredential(any(ArrayList.class));
+ verify(mAuthSecretService).setPrimaryUserCredential(any(byte[].class));
}
@Test
@@ -215,7 +256,7 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
initializeCredential(password, SECONDARY_USER_ID);
assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
password, SECONDARY_USER_ID, 0 /* flags */).getResponseCode());
- verify(mAuthSecretService, never()).primaryUserCredential(any(ArrayList.class));
+ verify(mAuthSecretService, never()).setPrimaryUserCredential(any(byte[].class));
}
@Test
@@ -226,7 +267,7 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
reset(mAuthSecretService);
mLocalService.unlockUserKeyIfUnsecured(PRIMARY_USER_ID);
- verify(mAuthSecretService).primaryUserCredential(any(ArrayList.class));
+ verify(mAuthSecretService).setPrimaryUserCredential(any(byte[].class));
}
@Test
@@ -360,6 +401,8 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
@Test
public void testEscrowTokenCannotBeActivatedOnUnmanagedUser() {
byte[] token = "some-high-entropy-secure-token".getBytes();
+ when(mDeviceStateCache.isUserOrganizationManaged(anyInt())).thenReturn(false);
+ // TODO(b/258213147): Remove
when(mUserManagerInternal.isDeviceManaged()).thenReturn(false);
when(mUserManagerInternal.isUserManaged(PRIMARY_USER_ID)).thenReturn(false);
when(mDeviceStateCache.isDeviceProvisioned()).thenReturn(true);
@@ -554,7 +597,7 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
initializeCredential(password, PRIMARY_USER_ID);
assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
password, PRIMARY_USER_ID, 0 /* flags */).getResponseCode());
- verify(mAuthSecretService, never()).primaryUserCredential(any(ArrayList.class));
+ verify(mAuthSecretService, never()).setPrimaryUserCredential(any(byte[].class));
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/WeaverBasedSyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/WeaverBasedSyntheticPasswordTests.java
index a3ac5153a03d..6c13a6fe04a0 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/WeaverBasedSyntheticPasswordTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/WeaverBasedSyntheticPasswordTests.java
@@ -1,11 +1,17 @@
package com.android.server.locksettings;
+import static org.junit.Assert.assertEquals;
+
import android.platform.test.annotations.Presubmit;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.server.locksettings.LockSettingsStorage.PersistentData;
+import com.google.android.collect.Sets;
+
import org.junit.Before;
+import org.junit.Test;
import org.junit.runner.RunWith;
@SmallTest
@@ -17,4 +23,36 @@ public class WeaverBasedSyntheticPasswordTests extends SyntheticPasswordTests {
public void enableWeaver() throws Exception {
mSpManager.enableWeaver();
}
+
+ // Tests that if the device is not yet provisioned and the FRP credential uses Weaver, then the
+ // Weaver slot of the FRP credential is not reused. Assumes that Weaver slots are allocated
+ // sequentially, starting at slot 0.
+ @Test
+ public void testFrpWeaverSlotNotReused() {
+ final int userId = 10;
+ final int frpWeaverSlot = 0;
+
+ setDeviceProvisioned(false);
+ assertEquals(Sets.newHashSet(), mPasswordSlotManager.getUsedSlots());
+ mStorage.writePersistentDataBlock(PersistentData.TYPE_SP_WEAVER, frpWeaverSlot, 0,
+ new byte[1]);
+ initializeSyntheticPassword(userId); // This should allocate a Weaver slot.
+ assertEquals(Sets.newHashSet(1), mPasswordSlotManager.getUsedSlots());
+ }
+
+ // Tests that if the device is already provisioned and the FRP credential uses Weaver, then the
+ // Weaver slot of the FRP credential is reused. This is not a very interesting test by itself;
+ // it's here as a control for testFrpWeaverSlotNotReused().
+ @Test
+ public void testFrpWeaverSlotReused() {
+ final int userId = 10;
+ final int frpWeaverSlot = 0;
+
+ setDeviceProvisioned(true);
+ assertEquals(Sets.newHashSet(), mPasswordSlotManager.getUsedSlots());
+ mStorage.writePersistentDataBlock(PersistentData.TYPE_SP_WEAVER, frpWeaverSlot, 0,
+ new byte[1]);
+ initializeSyntheticPassword(userId); // This should allocate a Weaver slot.
+ assertEquals(Sets.newHashSet(0), mPasswordSlotManager.getUsedSlots());
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java
index ab5292834fe6..578b888a6496 100644
--- a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java
@@ -323,4 +323,19 @@ public class OverlayManagerServiceImplTests extends OverlayManagerServiceImplTes
FakeIdmapDaemon.IdmapHeader idmap = idmapd.getIdmap(overlayPath);
assertEquals(0, CONFIG_SIGNATURE & idmap.policies);
}
+
+ @Test
+ public void testOnTargetSystemPackageUninstall() throws Exception {
+ installAndAssert(target(TARGET), USER,
+ Set.of(UserPackage.of(USER, TARGET)));
+ installAndAssert(overlay(OVERLAY, TARGET), USER,
+ Set.of(UserPackage.of(USER, OVERLAY), UserPackage.of(USER, TARGET)));
+ upgradeAndAssert(target(TARGET), USER,
+ Set.of(UserPackage.of(USER, TARGET)),
+ Set.of(UserPackage.of(USER, TARGET)));
+
+ downgradeAndAssert(target(TARGET), USER,
+ Set.of(UserPackage.of(USER, TARGET)),
+ Set.of(UserPackage.of(USER, TARGET)));
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java
index bba7669f3bbd..dab433558f22 100644
--- a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java
@@ -24,6 +24,7 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import android.annotation.NonNull;
+import android.content.Intent;
import android.content.om.OverlayIdentifier;
import android.content.om.OverlayInfo;
import android.content.om.OverlayInfo.State;
@@ -160,7 +161,7 @@ class OverlayManagerServiceImplTestsBase {
* Adds the package to the device.
*
* This corresponds to when the OMS receives the
- * {@link android.content.Intent#ACTION_PACKAGE_ADDED} broadcast.
+ * {@link Intent#ACTION_PACKAGE_ADDED} broadcast.
*
* @throws IllegalStateException if the package is currently installed
*/
@@ -178,10 +179,10 @@ class OverlayManagerServiceImplTestsBase {
* Begins upgrading the package.
*
* This corresponds to when the OMS receives the
- * {@link android.content.Intent#ACTION_PACKAGE_REMOVED} broadcast with the
- * {@link android.content.Intent#EXTRA_REPLACING} extra and then receives the
- * {@link android.content.Intent#ACTION_PACKAGE_ADDED} broadcast with the
- * {@link android.content.Intent#EXTRA_REPLACING} extra.
+ * {@link Intent#ACTION_PACKAGE_REMOVED} broadcast with the
+ * {@link Intent#EXTRA_REPLACING} extra and then receives the
+ * {@link Intent#ACTION_PACKAGE_ADDED} broadcast with the
+ * {@link Intent#EXTRA_REPLACING} extra.
*
* @throws IllegalStateException if the package is not currently installed
*/
@@ -194,7 +195,35 @@ class OverlayManagerServiceImplTestsBase {
throw new IllegalStateException("package " + pkg.packageName + " not installed");
}
- assertEquals(onReplacingUpdatedPackages, mImpl.onPackageReplacing(pkg.packageName, userId));
+ assertEquals(onReplacingUpdatedPackages, mImpl.onPackageReplacing(pkg.packageName,
+ /* systemUpdateUninstall */ false, userId));
+ mState.add(pkg, userId);
+ assertEquals(onReplacedUpdatedPackages, mImpl.onPackageReplaced(pkg.packageName, userId));
+ }
+
+ /**
+ * Begins downgrading the package. Usually used simulating a system uninstall of its /data
+ * variant.
+ *
+ * This corresponds to when the OMS receives the
+ * {@link Intent#ACTION_PACKAGE_REMOVED} broadcast with the
+ * {@link Intent#EXTRA_REPLACING} and {@link Intent#EXTRA_SYSTEM_UPDATE_UNINSTALL} extras
+ * and then receives the {@link Intent#ACTION_PACKAGE_ADDED} broadcast with the
+ * {@link Intent#EXTRA_REPLACING} extra.
+ *
+ * @throws IllegalStateException if the package is not currently installed
+ */
+ void downgradeAndAssert(FakeDeviceState.PackageBuilder pkg, int userId,
+ @NonNull Set<UserPackage> onReplacingUpdatedPackages,
+ @NonNull Set<UserPackage> onReplacedUpdatedPackages)
+ throws OperationFailedException {
+ final FakeDeviceState.Package replacedPackage = mState.select(pkg.packageName, userId);
+ if (replacedPackage == null) {
+ throw new IllegalStateException("package " + pkg.packageName + " not installed");
+ }
+
+ assertEquals(onReplacingUpdatedPackages, mImpl.onPackageReplacing(pkg.packageName,
+ /* systemUpdateUninstall */ true, userId));
mState.add(pkg, userId);
assertEquals(onReplacedUpdatedPackages, mImpl.onPackageReplaced(pkg.packageName, userId));
}
@@ -203,7 +232,7 @@ class OverlayManagerServiceImplTestsBase {
* Removes the package from the device.
*
* This corresponds to when the OMS receives the
- * {@link android.content.Intent#ACTION_PACKAGE_REMOVED} broadcast.
+ * {@link Intent#ACTION_PACKAGE_REMOVED} broadcast.
*
* @throws IllegalStateException if the package is not currently installed
*/
diff --git a/services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java b/services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java
new file mode 100644
index 000000000000..c81fbb443dce
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java
@@ -0,0 +1,781 @@
+package com.android.server.pm;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertThrows;
+
+import android.Manifest;
+import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
+import android.app.ActivityOptions;
+import android.app.AppOpsManager;
+import android.app.IApplicationThread;
+import android.app.admin.DevicePolicyManagerInternal;
+import android.content.AttributionSourceState;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.PermissionChecker;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.CrossProfileAppsInternal;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
+import android.content.pm.PermissionInfo;
+import android.content.pm.ResolveInfo;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.permission.PermissionCheckerManager;
+import android.permission.PermissionManager;
+import android.platform.test.annotations.Presubmit;
+import android.util.SparseArray;
+
+import com.android.internal.util.FunctionalUtils.ThrowingRunnable;
+import com.android.internal.util.FunctionalUtils.ThrowingSupplier;
+import com.android.server.LocalServices;
+import com.android.server.pm.permission.PermissionManagerService;
+import com.android.server.wm.ActivityTaskManagerInternal;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Build/Install/Run:
+ * atest FrameworksServicesTests:com.android.server.pm.CrossProfileAppsServiceImplTest
+ */
+@Presubmit
+@RunWith(MockitoJUnitRunner.class)
+public class CrossProfileAppsServiceImplTest {
+ private static final String PACKAGE_ONE = "com.one";
+ private static final String FEATURE_ID = "feature.one";
+ private static final int PACKAGE_ONE_UID = 1111;
+ private static final ComponentName ACTIVITY_COMPONENT =
+ new ComponentName("com.one", "test");
+
+ private static final String PACKAGE_TWO = "com.two";
+ private static final int PACKAGE_TWO_UID = 2222;
+
+ private static final int PRIMARY_USER = 0;
+ private static final int PROFILE_OF_PRIMARY_USER = 10;
+ private static final int SECONDARY_USER = 11;
+
+ @Mock
+ private Context mContext;
+ @Mock
+ private UserManager mUserManager;
+ @Mock
+ private PackageManager mPackageManager;
+ @Mock
+ private PackageManagerInternal mPackageManagerInternal;
+ @Mock
+ private AppOpsManager mAppOpsManager;
+ @Mock
+ private ActivityManagerInternal mActivityManagerInternal;
+ @Mock
+ private ActivityTaskManagerInternal mActivityTaskManagerInternal;
+ @Mock
+ private IPackageManager mIPackageManager;
+ @Mock
+ private DevicePolicyManagerInternal mDevicePolicyManagerInternal;
+
+ private TestInjector mTestInjector;
+ private ActivityInfo mActivityInfo;
+ private CrossProfileAppsServiceImpl mCrossProfileAppsServiceImpl;
+ private IApplicationThread mIApplicationThread;
+
+ private SparseArray<Boolean> mUserEnabled = new SparseArray<>();
+
+ @Before
+ public void initCrossProfileAppsServiceImpl() {
+ mTestInjector = new TestInjector();
+ LocalServices.removeServiceForTest(CrossProfileAppsInternal.class);
+ mCrossProfileAppsServiceImpl = new CrossProfileAppsServiceImpl(mContext, mTestInjector);
+ when(mContext.getPackageManager()).thenReturn(mPackageManager);
+ }
+
+ @Before
+ public void setupEnabledProfiles() {
+ mUserEnabled.put(PRIMARY_USER, true);
+ mUserEnabled.put(PROFILE_OF_PRIMARY_USER, true);
+ mUserEnabled.put(SECONDARY_USER, true);
+
+ when(mUserManager.getEnabledProfileIds(anyInt())).thenAnswer(
+ invocation -> {
+ List<Integer> users = new ArrayList<>();
+ final int targetUser = invocation.getArgument(0);
+ users.add(targetUser);
+
+ int profileUserId = -1;
+ if (targetUser == PRIMARY_USER) {
+ profileUserId = PROFILE_OF_PRIMARY_USER;
+ } else if (targetUser == PROFILE_OF_PRIMARY_USER) {
+ profileUserId = PRIMARY_USER;
+ }
+
+ if (profileUserId != -1 && mUserEnabled.get(profileUserId)) {
+ users.add(profileUserId);
+ }
+ return users.stream().mapToInt(i -> i).toArray();
+ });
+ }
+
+ @Before
+ public void setupCaller() {
+ mTestInjector.setCallingUid(PACKAGE_ONE_UID);
+ mTestInjector.setCallingUserId(PRIMARY_USER);
+ }
+
+ @Before
+ public void setupPackage() throws Exception {
+ // PACKAGE_ONE are installed in all users.
+ mockAppsInstalled(PACKAGE_ONE, PRIMARY_USER, true);
+ mockAppsInstalled(PACKAGE_ONE, PROFILE_OF_PRIMARY_USER, true);
+ mockAppsInstalled(PACKAGE_ONE, SECONDARY_USER, true);
+
+ // Packages are resolved to their corresponding UID.
+ doAnswer(invocation -> {
+ final int uid = invocation.getArgument(0);
+ final String packageName = invocation.getArgument(1);
+ if (uid == PACKAGE_ONE_UID && PACKAGE_ONE.equals(packageName)) {
+ return null;
+ } else if (uid ==PACKAGE_TWO_UID && PACKAGE_TWO.equals(packageName)) {
+ return null;
+ }
+ throw new SecurityException("Not matching");
+ }).when(mAppOpsManager).checkPackage(anyInt(), anyString());
+
+ // The intent is resolved to the ACTIVITY_COMPONENT.
+ mockActivityLaunchIntentResolvedTo(ACTIVITY_COMPONENT);
+ }
+
+ @Test
+ public void getTargetUserProfiles_fromPrimaryUser_installed() throws Exception {
+ List<UserHandle> targetProfiles =
+ mCrossProfileAppsServiceImpl.getTargetUserProfiles(PACKAGE_ONE);
+ assertThat(targetProfiles).containsExactly(UserHandle.of(PROFILE_OF_PRIMARY_USER));
+ }
+
+ @Test
+ public void getTargetUserProfiles_fromPrimaryUser_notInstalled() throws Exception {
+ mockAppsInstalled(PACKAGE_ONE, PROFILE_OF_PRIMARY_USER, false);
+
+ List<UserHandle> targetProfiles =
+ mCrossProfileAppsServiceImpl.getTargetUserProfiles(PACKAGE_ONE);
+ assertThat(targetProfiles).isEmpty();
+ }
+
+ @Test
+ public void getTargetUserProfiles_fromPrimaryUser_userNotEnabled() throws Exception {
+ mUserEnabled.put(PROFILE_OF_PRIMARY_USER, false);
+
+ List<UserHandle> targetProfiles =
+ mCrossProfileAppsServiceImpl.getTargetUserProfiles(PACKAGE_ONE);
+ assertThat(targetProfiles).isEmpty();
+ }
+
+ @Test
+ public void getTargetUserProfiles_fromSecondaryUser() throws Exception {
+ mTestInjector.setCallingUserId(SECONDARY_USER);
+
+ List<UserHandle> targetProfiles =
+ mCrossProfileAppsServiceImpl.getTargetUserProfiles(PACKAGE_ONE);
+ assertThat(targetProfiles).isEmpty();
+ }
+
+ @Test
+ public void getTargetUserProfiles_fromProfile_installed() throws Exception {
+ mTestInjector.setCallingUserId(PROFILE_OF_PRIMARY_USER);
+
+ List<UserHandle> targetProfiles =
+ mCrossProfileAppsServiceImpl.getTargetUserProfiles(PACKAGE_ONE);
+ assertThat(targetProfiles).containsExactly(UserHandle.of(PRIMARY_USER));
+ }
+
+ @Test
+ public void getTargetUserProfiles_fromProfile_notInstalled() throws Exception {
+ mTestInjector.setCallingUserId(PROFILE_OF_PRIMARY_USER);
+ mockAppsInstalled(PACKAGE_ONE, PRIMARY_USER, false);
+
+ List<UserHandle> targetProfiles =
+ mCrossProfileAppsServiceImpl.getTargetUserProfiles(PACKAGE_ONE);
+ assertThat(targetProfiles).isEmpty();
+ }
+
+ @Test(expected = SecurityException.class)
+ public void getTargetUserProfiles_fakeCaller() throws Exception {
+ mCrossProfileAppsServiceImpl.getTargetUserProfiles(PACKAGE_TWO);
+ }
+
+ @Test
+ public void startActivityAsUser_currentUser() throws Exception {
+ assertThrows(
+ SecurityException.class,
+ () ->
+ mCrossProfileAppsServiceImpl.startActivityAsUser(
+ mIApplicationThread,
+ PACKAGE_ONE,
+ FEATURE_ID,
+ ACTIVITY_COMPONENT,
+ UserHandle.of(PRIMARY_USER).getIdentifier(),
+ true,
+ /* targetTask */ null,
+ /* options */ null));
+
+ verify(mActivityTaskManagerInternal, never())
+ .startActivityAsUser(
+ nullable(IApplicationThread.class),
+ anyString(),
+ nullable(String.class),
+ any(Intent.class),
+ nullable(IBinder.class),
+ anyInt(),
+ nullable(Bundle.class),
+ anyInt());
+ }
+
+ @Test
+ public void startAnyActivityAsUser_currentUser() {
+ assertThrows(
+ SecurityException.class,
+ () ->
+ mCrossProfileAppsServiceImpl.startActivityAsUser(
+ mIApplicationThread,
+ PACKAGE_ONE,
+ FEATURE_ID,
+ ACTIVITY_COMPONENT,
+ UserHandle.of(PRIMARY_USER).getIdentifier(),
+ false,
+ /* targetTask */ null,
+ /* options */ null));
+
+ verify(mActivityTaskManagerInternal, never())
+ .startActivityAsUser(
+ nullable(IApplicationThread.class),
+ anyString(),
+ nullable(String.class),
+ any(Intent.class),
+ nullable(IBinder.class),
+ anyInt(),
+ nullable(Bundle.class),
+ anyInt());
+ }
+
+ @Test
+ public void startActivityAsUser_profile_notInstalled() throws Exception {
+ mockAppsInstalled(PACKAGE_ONE, PROFILE_OF_PRIMARY_USER, false);
+
+ assertThrows(
+ SecurityException.class,
+ () ->
+ mCrossProfileAppsServiceImpl.startActivityAsUser(
+ mIApplicationThread,
+ PACKAGE_ONE,
+ FEATURE_ID,
+ ACTIVITY_COMPONENT,
+ UserHandle.of(PROFILE_OF_PRIMARY_USER).getIdentifier(),
+ true,
+ /* targetTask */ null,
+ /* options */ null));
+
+ verify(mActivityTaskManagerInternal, never())
+ .startActivityAsUser(
+ nullable(IApplicationThread.class),
+ anyString(),
+ nullable(String.class),
+ any(Intent.class),
+ nullable(IBinder.class),
+ anyInt(),
+ nullable(Bundle.class),
+ anyInt());
+ }
+
+ @Test
+ public void startAnyActivityAsUser_profile_notInstalled() {
+ mockAppsInstalled(PACKAGE_ONE, PROFILE_OF_PRIMARY_USER, false);
+
+ assertThrows(
+ SecurityException.class,
+ () ->
+ mCrossProfileAppsServiceImpl.startActivityAsUser(
+ mIApplicationThread,
+ PACKAGE_ONE,
+ FEATURE_ID,
+ ACTIVITY_COMPONENT,
+ UserHandle.of(PROFILE_OF_PRIMARY_USER).getIdentifier(),
+ false,
+ /* targetTask */ null,
+ /* options */ null));
+
+ verify(mActivityTaskManagerInternal, never())
+ .startActivityAsUser(
+ nullable(IApplicationThread.class),
+ anyString(),
+ nullable(String.class),
+ any(Intent.class),
+ nullable(IBinder.class),
+ anyInt(),
+ nullable(Bundle.class),
+ anyInt());
+ }
+
+ @Test
+ public void startActivityAsUser_profile_fakeCaller() throws Exception {
+ assertThrows(
+ SecurityException.class,
+ () ->
+ mCrossProfileAppsServiceImpl.startActivityAsUser(
+ mIApplicationThread,
+ PACKAGE_TWO,
+ FEATURE_ID,
+ ACTIVITY_COMPONENT,
+ UserHandle.of(PROFILE_OF_PRIMARY_USER).getIdentifier(),
+ true,
+ /* targetTask */ null,
+ /* options */ null));
+
+ verify(mActivityTaskManagerInternal, never())
+ .startActivityAsUser(
+ nullable(IApplicationThread.class),
+ anyString(),
+ nullable(String.class),
+ any(Intent.class),
+ nullable(IBinder.class),
+ anyInt(),
+ nullable(Bundle.class),
+ anyInt());
+ }
+
+ @Test
+ public void startAnyActivityAsUser_profile_fakeCaller() {
+ assertThrows(
+ SecurityException.class,
+ () ->
+ mCrossProfileAppsServiceImpl.startActivityAsUser(
+ mIApplicationThread,
+ PACKAGE_TWO,
+ FEATURE_ID,
+ ACTIVITY_COMPONENT,
+ UserHandle.of(PROFILE_OF_PRIMARY_USER).getIdentifier(),
+ false,
+ /* targetTask */ null,
+ /* options */ null));
+
+ verify(mActivityTaskManagerInternal, never())
+ .startActivityAsUser(
+ nullable(IApplicationThread.class),
+ anyString(),
+ nullable(String.class),
+ any(Intent.class),
+ nullable(IBinder.class),
+ anyInt(),
+ nullable(Bundle.class),
+ anyInt());
+ }
+
+ @Test
+ public void startActivityAsUser_profile_notExported() throws Exception {
+ mActivityInfo.exported = false;
+
+ assertThrows(
+ SecurityException.class,
+ () ->
+ mCrossProfileAppsServiceImpl.startActivityAsUser(
+ mIApplicationThread,
+ PACKAGE_ONE,
+ FEATURE_ID,
+ ACTIVITY_COMPONENT,
+ UserHandle.of(PROFILE_OF_PRIMARY_USER).getIdentifier(),
+ true,
+ /* targetTask */ null,
+ /* options */ null));
+
+ verify(mActivityTaskManagerInternal, never())
+ .startActivityAsUser(
+ nullable(IApplicationThread.class),
+ anyString(),
+ nullable(String.class),
+ any(Intent.class),
+ nullable(IBinder.class),
+ anyInt(),
+ nullable(Bundle.class),
+ anyInt());
+ }
+
+ @Test
+ public void startAnyActivityAsUser_profile_notExported() {
+ try {
+ when(mPackageManager.getPermissionInfo(anyString(), anyInt()))
+ .thenReturn(new PermissionInfo());
+ } catch (PackageManager.NameNotFoundException ignored) {
+ }
+ mActivityInfo.exported = false;
+
+
+ // There's a bug in static mocking if the APK is large - so here is the next best thing...
+ doReturn(Context.PERMISSION_CHECKER_SERVICE).when(mContext)
+ .getSystemServiceName(PermissionCheckerManager.class);
+ PermissionCheckerManager permissionCheckerManager = mock(PermissionCheckerManager.class);
+ doReturn(PermissionChecker.PERMISSION_HARD_DENIED).when(permissionCheckerManager)
+ .checkPermission(eq(Manifest.permission.INTERACT_ACROSS_PROFILES), any(
+ AttributionSourceState.class), anyString(), anyBoolean(), anyBoolean(),
+ anyBoolean(), anyInt());
+ doReturn(permissionCheckerManager).when(mContext).getSystemService(
+ Context.PERMISSION_CHECKER_SERVICE);
+
+ assertThrows(
+ SecurityException.class,
+ () ->
+ mCrossProfileAppsServiceImpl.startActivityAsUser(
+ mIApplicationThread,
+ PACKAGE_ONE,
+ FEATURE_ID,
+ ACTIVITY_COMPONENT,
+ UserHandle.of(PROFILE_OF_PRIMARY_USER).getIdentifier(),
+ false,
+ /* targetTask */ null,
+ /* options */ null));
+
+ verify(mActivityTaskManagerInternal, never())
+ .startActivityAsUser(
+ nullable(IApplicationThread.class),
+ anyString(),
+ nullable(String.class),
+ any(Intent.class),
+ nullable(IBinder.class),
+ anyInt(),
+ nullable(Bundle.class),
+ anyInt());
+ }
+
+ @Test
+ public void startActivityAsUser_profile_anotherPackage() throws Exception {
+ assertThrows(
+ SecurityException.class,
+ () ->
+ mCrossProfileAppsServiceImpl.startActivityAsUser(
+ mIApplicationThread,
+ PACKAGE_ONE,
+ FEATURE_ID,
+ new ComponentName(PACKAGE_TWO, "test"),
+ UserHandle.of(PROFILE_OF_PRIMARY_USER).getIdentifier(),
+ true,
+ /* targetTask */ null,
+ /* options */ null));
+
+ verify(mActivityTaskManagerInternal, never())
+ .startActivityAsUser(
+ nullable(IApplicationThread.class),
+ anyString(),
+ nullable(String.class),
+ any(Intent.class),
+ nullable(IBinder.class),
+ anyInt(),
+ nullable(Bundle.class),
+ anyInt());
+ }
+
+ @Test
+ public void startAnyActivityAsUser_profile_anotherPackage() {
+ assertThrows(
+ SecurityException.class,
+ () ->
+ mCrossProfileAppsServiceImpl.startActivityAsUser(
+ mIApplicationThread,
+ PACKAGE_ONE,
+ FEATURE_ID,
+ new ComponentName(PACKAGE_TWO, "test"),
+ UserHandle.of(PROFILE_OF_PRIMARY_USER).getIdentifier(),
+ false,
+ /* targetTask */ null,
+ /* options */ null));
+
+ verify(mActivityTaskManagerInternal, never())
+ .startActivityAsUser(
+ nullable(IApplicationThread.class),
+ anyString(),
+ nullable(String.class),
+ any(Intent.class),
+ nullable(IBinder.class),
+ anyInt(),
+ nullable(Bundle.class),
+ anyInt());
+ }
+
+ @Test
+ public void startActivityAsUser_secondaryUser() throws Exception {
+ assertThrows(
+ SecurityException.class,
+ () ->
+ mCrossProfileAppsServiceImpl.startActivityAsUser(
+ mIApplicationThread,
+ PACKAGE_ONE,
+ FEATURE_ID,
+ ACTIVITY_COMPONENT,
+ UserHandle.of(SECONDARY_USER).getIdentifier(),
+ true,
+ /* targetTask */ null,
+ /* options */ null));
+
+ verify(mActivityTaskManagerInternal, never())
+ .startActivityAsUser(
+ nullable(IApplicationThread.class),
+ anyString(),
+ nullable(String.class),
+ any(Intent.class),
+ nullable(IBinder.class),
+ anyInt(),
+ nullable(Bundle.class),
+ anyInt());
+ }
+
+ @Test
+ public void startAnyActivityAsUser_secondaryUser() {
+ assertThrows(
+ SecurityException.class,
+ () ->
+ mCrossProfileAppsServiceImpl.startActivityAsUser(
+ mIApplicationThread,
+ PACKAGE_ONE,
+ FEATURE_ID,
+ ACTIVITY_COMPONENT,
+ UserHandle.of(SECONDARY_USER).getIdentifier(),
+ false,
+ /* targetTask */ null,
+ /* options */ null));
+
+ verify(mActivityTaskManagerInternal, never())
+ .startActivityAsUser(
+ nullable(IApplicationThread.class),
+ anyString(),
+ nullable(String.class),
+ any(Intent.class),
+ nullable(IBinder.class),
+ anyInt(),
+ nullable(Bundle.class),
+ anyInt());
+ }
+
+ @Test
+ public void startActivityAsUser_fromProfile_success() throws Exception {
+ mTestInjector.setCallingUserId(PROFILE_OF_PRIMARY_USER);
+
+ mCrossProfileAppsServiceImpl.startActivityAsUser(
+ mIApplicationThread,
+ PACKAGE_ONE,
+ FEATURE_ID,
+ ACTIVITY_COMPONENT,
+ UserHandle.of(PRIMARY_USER).getIdentifier(),
+ true,
+ /* targetTask */ null,
+ /* options */ null);
+
+ verify(mActivityTaskManagerInternal)
+ .startActivityAsUser(
+ nullable(IApplicationThread.class),
+ eq(PACKAGE_ONE),
+ eq(FEATURE_ID),
+ any(Intent.class),
+ nullable(IBinder.class),
+ anyInt(),
+ nullable(Bundle.class),
+ eq(PRIMARY_USER));
+ }
+
+ @Test
+ public void startActivityAsUser_sameTask_fromProfile_success() throws Exception {
+ mTestInjector.setCallingUserId(PROFILE_OF_PRIMARY_USER);
+
+ Bundle options = ActivityOptions.makeOpenCrossProfileAppsAnimation().toBundle();
+ Binder targetTask = new Binder();
+ mCrossProfileAppsServiceImpl.startActivityAsUser(
+ mIApplicationThread,
+ PACKAGE_ONE,
+ FEATURE_ID,
+ ACTIVITY_COMPONENT,
+ UserHandle.of(PRIMARY_USER).getIdentifier(),
+ true,
+ targetTask,
+ options);
+ verify(mActivityTaskManagerInternal)
+ .startActivityAsUser(
+ nullable(IApplicationThread.class),
+ eq(PACKAGE_ONE),
+ eq(FEATURE_ID),
+ any(Intent.class),
+ eq(targetTask),
+ anyInt(),
+ eq(options),
+ eq(PRIMARY_USER));
+ }
+
+ private void mockAppsInstalled(String packageName, int user, boolean installed) {
+ when(mPackageManagerInternal.getPackageInfo(
+ eq(packageName),
+ anyLong(),
+ anyInt(),
+ eq(user)))
+ .thenReturn(installed ? createInstalledPackageInfo() : null);
+ }
+
+ private PackageInfo createInstalledPackageInfo() {
+ PackageInfo packageInfo = new PackageInfo();
+ packageInfo.applicationInfo = new ApplicationInfo();
+ packageInfo.applicationInfo.enabled = true;
+ return packageInfo;
+ }
+
+ private void mockActivityLaunchIntentResolvedTo(ComponentName componentName) {
+ ResolveInfo resolveInfo = new ResolveInfo();
+ ActivityInfo activityInfo = new ActivityInfo();
+ activityInfo.packageName = componentName.getPackageName();
+ activityInfo.name = componentName.getClassName();
+ activityInfo.exported = true;
+ resolveInfo.activityInfo = activityInfo;
+ mActivityInfo = activityInfo;
+
+ when(mPackageManagerInternal.queryIntentActivities(
+ any(Intent.class), nullable(String.class), anyLong(), anyInt(), anyInt()))
+ .thenReturn(Collections.singletonList(resolveInfo));
+ }
+
+ private class TestInjector implements CrossProfileAppsServiceImpl.Injector {
+ private int mCallingUid;
+ private int mCallingUserId;
+ private int mCallingPid;
+
+ public void setCallingUid(int uid) {
+ mCallingUid = uid;
+ }
+
+ public void setCallingPid(int pid) {
+ mCallingPid = pid;
+ }
+
+ public void setCallingUserId(int userId) {
+ mCallingUserId = userId;
+ }
+
+ @Override
+ public int getCallingUid() {
+ return mCallingUid;
+ }
+
+ @Override
+ public int getCallingPid() {
+ return mCallingPid;
+ }
+
+ @Override
+ public int getCallingUserId() {
+ return mCallingUserId;
+ }
+
+ @Override
+ public UserHandle getCallingUserHandle() {
+ return UserHandle.of(mCallingUserId);
+ }
+
+ @Override
+ public long clearCallingIdentity() {
+ return 0;
+ }
+
+ @Override
+ public void restoreCallingIdentity(long token) {
+ }
+
+ @Override
+ public void withCleanCallingIdentity(ThrowingRunnable action) {
+ action.run();
+ }
+
+ @Override
+ public <T> T withCleanCallingIdentity(ThrowingSupplier<T> action) {
+ return action.get();
+ }
+
+ @Override
+ public UserManager getUserManager() {
+ return mUserManager;
+ }
+
+ @Override
+ public PackageManagerInternal getPackageManagerInternal() {
+ return mPackageManagerInternal;
+ }
+
+ @Override
+ public PackageManager getPackageManager() {
+ return mPackageManager;
+ }
+
+ @Override
+ public AppOpsManager getAppOpsManager() {
+ return mAppOpsManager;
+ }
+
+ @Override
+ public ActivityManagerInternal getActivityManagerInternal() {
+ return mActivityManagerInternal;
+ }
+
+ @Override
+ public ActivityTaskManagerInternal getActivityTaskManagerInternal() {
+ return mActivityTaskManagerInternal;
+ }
+
+ @Override
+ public IPackageManager getIPackageManager() {
+ return mIPackageManager;
+ }
+
+ @Override
+ public DevicePolicyManagerInternal getDevicePolicyManagerInternal() {
+ return mDevicePolicyManagerInternal;
+ }
+
+ @Override
+ public void sendBroadcastAsUser(Intent intent, UserHandle user) {
+ mContext.sendBroadcastAsUser(intent, user);
+ }
+
+ @Override
+ public int checkComponentPermission(
+ String permission, int uid, int owningUid, boolean exported) {
+ return ActivityManager.checkComponentPermission(permission, uid, owningUid, exported);
+ }
+
+ @Override
+ public void killUid(int uid) {
+ PermissionManagerService.killUid(
+ UserHandle.getAppId(uid),
+ UserHandle.getUserId(uid),
+ PermissionManager.KILL_APP_REASON_PERMISSIONS_REVOKED);
+ }
+ }
+} \ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/power/PowerGroupTest.java b/services/tests/servicestests/src/com/android/server/power/PowerGroupTest.java
index e3ca1707ae0c..b034b0da387f 100644
--- a/services/tests/servicestests/src/com/android/server/power/PowerGroupTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/PowerGroupTest.java
@@ -21,7 +21,6 @@ import static android.hardware.display.DisplayManagerInternal.DisplayPowerReques
import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_DIM;
import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE;
import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_OFF;
-import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_VR;
import static android.os.PowerManager.GO_TO_SLEEP_REASON_APPLICATION;
import static android.os.PowerManager.GO_TO_SLEEP_REASON_DEVICE_ADMIN;
import static android.os.PowerManager.GO_TO_SLEEP_REASON_DEVICE_FOLD;
@@ -267,7 +266,6 @@ public class PowerGroupTest {
powerSaveState,
/* quiescent= */ false,
/* dozeAfterScreenOff= */ false,
- /* vrModeEnabled= */ false,
/* bootCompleted= */ true,
/* screenBrightnessBoostInProgress= */ false,
/* waitForNegativeProximity= */ false);
@@ -308,7 +306,6 @@ public class PowerGroupTest {
powerSaveState,
/* quiescent= */ false,
/* dozeAfterScreenOff= */ false,
- /* vrModeEnabled= */ false,
/* bootCompleted= */ true,
/* screenBrightnessBoostInProgress= */ false,
/* waitForNegativeProximity= */ false);
@@ -348,7 +345,6 @@ public class PowerGroupTest {
powerSaveState,
/* quiescent= */ false,
/* dozeAfterScreenOff= */ true,
- /* vrModeEnabled= */ false,
/* bootCompleted= */ true,
/* screenBrightnessBoostInProgress= */ false,
/* waitForNegativeProximity= */ false);
@@ -387,7 +383,6 @@ public class PowerGroupTest {
powerSaveState,
/* quiescent= */ true,
/* dozeAfterScreenOff= */ true,
- /* vrModeEnabled= */ false,
/* bootCompleted= */ true,
/* screenBrightnessBoostInProgress= */ false,
/* waitForNegativeProximity= */ false);
@@ -407,83 +402,6 @@ public class PowerGroupTest {
}
@Test
- public void testUpdateWhileAsleep_VrModeEnabled() {
- final boolean batterySaverEnabled = false;
- float brightnessFactor = 0.3f;
- PowerSaveState powerSaveState = new PowerSaveState.Builder()
- .setBatterySaverEnabled(batterySaverEnabled)
- .setBrightnessFactor(brightnessFactor)
- .build();
- mPowerGroup.sleepLocked(TIMESTAMP1, UID, GO_TO_SLEEP_REASON_TIMEOUT);
- assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
- mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
- /* autoBrightness = */ true,
- /* useProximitySensor= */ true,
- /* boostScreenBrightness= */ true,
- /* dozeScreenStateOverride= */ Display.STATE_ON,
- /* dozeScreenBrightness= */ BRIGHTNESS_DOZE,
- /* overrideDrawWakeLock= */ false,
- powerSaveState,
- /* quiescent= */ false,
- /* dozeAfterScreenOff= */ true,
- /* vrModeEnabled= */ true,
- /* bootCompleted= */ true,
- /* screenBrightnessBoostInProgress= */ false,
- /* waitForNegativeProximity= */ false);
- DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
- mPowerGroup.mDisplayPowerRequest;
- assertThat(displayPowerRequest.policy).isEqualTo(POLICY_OFF);
- assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
- assertThat(displayPowerRequest.useAutoBrightness).isEqualTo(true);
- assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
- assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
- assertThat(displayPowerRequest.dozeScreenState).isEqualTo(Display.STATE_UNKNOWN);
- assertThat(displayPowerRequest.dozeScreenBrightness).isEqualTo(
- PowerManager.BRIGHTNESS_INVALID_FLOAT);
- assertThat(displayPowerRequest.lowPowerMode).isEqualTo(batterySaverEnabled);
- assertThat(displayPowerRequest.screenLowPowerBrightnessFactor).isWithin(PRECISION).of(
- brightnessFactor);
- }
-
- @Test
- public void testUpdateWhileAwake_VrModeEnabled() {
- final boolean batterySaverEnabled = false;
- float brightnessFactor = 0.3f;
- PowerSaveState powerSaveState = new PowerSaveState.Builder()
- .setBatterySaverEnabled(batterySaverEnabled)
- .setBrightnessFactor(brightnessFactor)
- .build();
- assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
- mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
- /* autoBrightness = */ true,
- /* useProximitySensor= */ true,
- /* boostScreenBrightness= */ true,
- /* dozeScreenStateOverride= */ Display.STATE_ON,
- /* dozeScreenBrightness= */ BRIGHTNESS_DOZE,
- /* overrideDrawWakeLock= */ false,
- powerSaveState,
- /* quiescent= */ false,
- /* dozeAfterScreenOff= */ true,
- /* vrModeEnabled= */ true,
- /* bootCompleted= */ true,
- /* screenBrightnessBoostInProgress= */ false,
- /* waitForNegativeProximity= */ false);
- DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
- mPowerGroup.mDisplayPowerRequest;
- assertThat(displayPowerRequest.policy).isEqualTo(POLICY_VR);
- assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
- assertThat(displayPowerRequest.useAutoBrightness).isEqualTo(true);
- assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
- assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
- assertThat(displayPowerRequest.dozeScreenState).isEqualTo(Display.STATE_UNKNOWN);
- assertThat(displayPowerRequest.dozeScreenBrightness).isEqualTo(
- PowerManager.BRIGHTNESS_INVALID_FLOAT);
- assertThat(displayPowerRequest.lowPowerMode).isEqualTo(batterySaverEnabled);
- assertThat(displayPowerRequest.screenLowPowerBrightnessFactor).isWithin(PRECISION).of(
- brightnessFactor);
- }
-
- @Test
public void testUpdateWhileAsleep_UpdatesDisplayPowerRequest() {
final boolean batterySaverEnabled = false;
float brightnessFactor = 0.3f;
@@ -503,7 +421,6 @@ public class PowerGroupTest {
powerSaveState,
/* quiescent= */ false,
/* dozeAfterScreenOff= */ false,
- /* vrModeEnabled= */ false,
/* bootCompleted= */ true,
/* screenBrightnessBoostInProgress= */ false,
/* waitForNegativeProximity= */ false);
@@ -543,7 +460,6 @@ public class PowerGroupTest {
powerSaveState,
/* quiescent= */ false,
/* dozeAfterScreenOff= */ false,
- /* vrModeEnabled= */ false,
/* bootCompleted= */ true,
/* screenBrightnessBoostInProgress= */ false,
/* waitForNegativeProximity= */ false);
@@ -581,7 +497,6 @@ public class PowerGroupTest {
powerSaveState,
/* quiescent= */ false,
/* dozeAfterScreenOff= */ false,
- /* vrModeEnabled= */ false,
/* bootCompleted= */ false,
/* screenBrightnessBoostInProgress= */ false,
/* waitForNegativeProximity= */ false);
@@ -620,7 +535,6 @@ public class PowerGroupTest {
powerSaveState,
/* quiescent= */ false,
/* dozeAfterScreenOff= */ false,
- /* vrModeEnabled= */ false,
/* bootCompleted= */ true,
/* screenBrightnessBoostInProgress= */ false,
/* waitForNegativeProximity= */ false);
@@ -658,7 +572,6 @@ public class PowerGroupTest {
powerSaveState,
/* quiescent= */ false,
/* dozeAfterScreenOff= */ false,
- /* vrModeEnabled= */ false,
/* bootCompleted= */ true,
/* screenBrightnessBoostInProgress= */ true,
/* waitForNegativeProximity= */ false);
diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
index 6258d6d29ae0..d7ff5536ce3f 100644
--- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -454,39 +454,6 @@ public class PowerManagerServiceTest {
}
@Test
- public void testGetDesiredScreenPolicy_WithVR() {
- createService();
- startSystem();
- // Brighten up the screen
- mService.setWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP, WAKEFULNESS_AWAKE, 0, 0, 0, 0,
- null, null);
- assertThat(mService.getDesiredScreenPolicyLocked(Display.DEFAULT_DISPLAY)).isEqualTo(
- DisplayPowerRequest.POLICY_BRIGHT);
-
- // Move to VR
- mService.setVrModeEnabled(true);
- assertThat(mService.getDesiredScreenPolicyLocked(Display.DEFAULT_DISPLAY)).isEqualTo(
- DisplayPowerRequest.POLICY_VR);
-
- // Then take a nap
- mService.setWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP, WAKEFULNESS_ASLEEP, 0, 0, 0, 0,
- null, null);
- assertThat(mService.getDesiredScreenPolicyLocked(Display.DEFAULT_DISPLAY)).isEqualTo(
- DisplayPowerRequest.POLICY_OFF);
-
- // Wake up to VR
- mService.setWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP, WAKEFULNESS_AWAKE, 0, 0, 0, 0,
- null, null);
- assertThat(mService.getDesiredScreenPolicyLocked(Display.DEFAULT_DISPLAY)).isEqualTo(
- DisplayPowerRequest.POLICY_VR);
-
- // And back to normal
- mService.setVrModeEnabled(false);
- assertThat(mService.getDesiredScreenPolicyLocked(Display.DEFAULT_DISPLAY)).isEqualTo(
- DisplayPowerRequest.POLICY_BRIGHT);
- }
-
- @Test
public void testWakefulnessAwake_InitialValue() {
createService();
assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
@@ -1787,10 +1754,8 @@ public class PowerManagerServiceTest {
when(mNativeWrapperMock.nativeSetPowerMode(anyInt(), anyBoolean())).thenReturn(true);
mService.getBinderServiceInstance().setPowerMode(Mode.LAUNCH, true);
- mService.getBinderServiceInstance().setPowerMode(Mode.VR, false);
verify(mNativeWrapperMock).nativeSetPowerMode(eq(Mode.LAUNCH), eq(true));
- verify(mNativeWrapperMock).nativeSetPowerMode(eq(Mode.VR), eq(false));
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsRule.java b/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
index 1815e2a42dbb..7c1962c7dfae 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
@@ -21,6 +21,7 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
+import android.annotation.XmlRes;
import android.content.Context;
import android.net.NetworkStats;
import android.os.BatteryConsumer;
@@ -50,6 +51,7 @@ public class BatteryUsageStatsRule implements TestRule {
.powerProfileModeledOnly()
.includePowerModels()
.build();
+ private final Context mContext;
private final PowerProfile mPowerProfile;
private final MockClock mMockClock = new MockClock();
@@ -67,14 +69,19 @@ public class BatteryUsageStatsRule implements TestRule {
}
public BatteryUsageStatsRule(long currentTime, File historyDir) {
- Context context = InstrumentationRegistry.getContext();
- mPowerProfile = spy(new PowerProfile(context, true /* forTest */));
+ mContext = InstrumentationRegistry.getContext();
+ mPowerProfile = spy(new PowerProfile(mContext, true /* forTest */));
mMockClock.currentTime = currentTime;
mBatteryStats = new MockBatteryStatsImpl(mMockClock, historyDir);
mBatteryStats.setPowerProfile(mPowerProfile);
mBatteryStats.onSystemReady();
}
+ public BatteryUsageStatsRule setTestPowerProfile(@XmlRes int xmlId) {
+ mPowerProfile.forceInitForTesting(mContext, xmlId);
+ return this;
+ }
+
public BatteryUsageStatsRule setAveragePower(String key, double value) {
when(mPowerProfile.getAveragePower(key)).thenReturn(value);
when(mPowerProfile.getAveragePowerOrDefault(eq(key), anyDouble())).thenReturn(value);
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/MobileRadioPowerCalculatorTest.java b/services/tests/servicestests/src/com/android/server/power/stats/MobileRadioPowerCalculatorTest.java
index 8262fcabe4cf..5bc73bd76d4a 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/MobileRadioPowerCalculatorTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/MobileRadioPowerCalculatorTest.java
@@ -27,6 +27,7 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import android.app.usage.NetworkStatsManager;
+import android.hardware.radio.V1_5.AccessNetwork;
import android.net.NetworkCapabilities;
import android.net.NetworkStats;
import android.os.BatteryConsumer;
@@ -34,6 +35,8 @@ import android.os.BatteryStats;
import android.os.BatteryUsageStatsQuery;
import android.os.Process;
import android.os.UidBatteryConsumer;
+import android.telephony.ActivityStatsTechSpecificInfo;
+import android.telephony.CellSignalStrength;
import android.telephony.DataConnectionRealTimeInfo;
import android.telephony.ModemActivityInfo;
import android.telephony.ServiceState;
@@ -43,7 +46,7 @@ import android.telephony.TelephonyManager;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
-import com.android.internal.os.PowerProfile;
+import com.android.frameworks.servicestests.R;
import com.google.common.collect.Range;
@@ -52,28 +55,25 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
+import java.util.ArrayList;
+
@RunWith(AndroidJUnit4.class)
@SmallTest
@SuppressWarnings("GuardedBy")
public class MobileRadioPowerCalculatorTest {
private static final double PRECISION = 0.00001;
private static final int APP_UID = Process.FIRST_APPLICATION_UID + 42;
+ private static final int APP_UID2 = Process.FIRST_APPLICATION_UID + 101;
@Mock
NetworkStatsManager mNetworkStatsManager;
@Rule
- public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
- .setAveragePowerUnspecified(PowerProfile.POWER_RADIO_ACTIVE)
- .setAveragePowerUnspecified(PowerProfile.POWER_RADIO_ON)
- .setAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_IDLE, 360.0)
- .setAveragePower(PowerProfile.POWER_RADIO_SCANNING, 720.0)
- .setAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_RX, 1440.0)
- .setAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_TX,
- new double[]{720.0, 1080.0, 1440.0, 1800.0, 2160.0})
- .initMeasuredEnergyStatsLocked();
+ public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule();
@Test
public void testCounterBasedModel() {
+ mStatsRule.setTestPowerProfile(R.xml.power_profile_test_modem_calculator)
+ .initMeasuredEnergyStatsLocked();
BatteryStatsImpl stats = mStatsRule.getBatteryStats();
// Scan for a cell
@@ -85,9 +85,15 @@ public class MobileRadioPowerCalculatorTest {
stats.notePhoneStateLocked(ServiceState.STATE_IN_SERVICE, TelephonyManager.SIM_STATE_READY,
5000, 5000);
+ ArrayList<CellSignalStrength> perRatCellStrength = new ArrayList();
+ CellSignalStrength gsmSignalStrength = mock(CellSignalStrength.class);
+ when(gsmSignalStrength.getLevel()).thenReturn(
+ SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
+ perRatCellStrength.add(gsmSignalStrength);
+
// Note cell signal strength
SignalStrength signalStrength = mock(SignalStrength.class);
- when(signalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_MODERATE);
+ when(signalStrength.getCellSignalStrengths()).thenReturn(perRatCellStrength);
stats.notePhoneSignalStrengthLocked(signalStrength, 5000, 5000);
stats.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH,
@@ -97,10 +103,31 @@ public class MobileRadioPowerCalculatorTest {
stats.noteNetworkInterfaceForTransports("cellular",
new int[]{NetworkCapabilities.TRANSPORT_CELLULAR});
+ // Spend some time in each signal strength level. It doesn't matter how long.
+ // The ModemActivityInfo reported level residency should be trusted over the BatteryStats
+ // timers.
+ when(gsmSignalStrength.getLevel()).thenReturn(
+ SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
+ stats.notePhoneSignalStrengthLocked(signalStrength, 8111, 8111);
+ when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_POOR);
+ stats.notePhoneSignalStrengthLocked(signalStrength, 8333, 8333);
+ when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_MODERATE);
+ stats.notePhoneSignalStrengthLocked(signalStrength, 8666, 8666);
+ when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_GOOD);
+ stats.notePhoneSignalStrengthLocked(signalStrength, 9110, 9110);
+
+ stats.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH,
+ 9_500_000_000L, APP_UID2, 9500, 9500);
+
+ when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_GREAT);
+ stats.notePhoneSignalStrengthLocked(signalStrength, 9665, 9665);
+
// Note application network activity
NetworkStats networkStats = new NetworkStats(10000, 1)
.addEntry(new NetworkStats.Entry("cellular", APP_UID, 0, 0,
- METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 100, 2000, 20, 100));
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 150, 2000, 30, 100))
+ .addEntry(new NetworkStats.Entry("cellular", APP_UID2, 0, 0,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 500, 50, 300, 10, 111));
mStatsRule.setNetworkStats(networkStats);
ModemActivityInfo mai = new ModemActivityInfo(10000, 2000, 3000,
@@ -108,34 +135,331 @@ public class MobileRadioPowerCalculatorTest {
stats.noteModemControllerActivity(mai, POWER_DATA_UNAVAILABLE, 10000, 10000,
mNetworkStatsManager);
- mStatsRule.setTime(12_000, 12_000);
+ mStatsRule.setTime(10_000, 10_000);
+
+ MobileRadioPowerCalculator calculator =
+ new MobileRadioPowerCalculator(mStatsRule.getPowerProfile());
+
+ mStatsRule.apply(BatteryUsageStatsRule.POWER_PROFILE_MODEL_ONLY, calculator);
+
+ BatteryConsumer deviceConsumer = mStatsRule.getDeviceBatteryConsumer();
+ // 720 mA * 100 ms (level 0 TX drain rate * level 0 TX duration)
+ // + 1080 mA * 200 ms (level 1 TX drain rate * level 1 TX duration)
+ // + 1440 mA * 300 ms (level 2 TX drain rate * level 2 TX duration)
+ // + 1800 mA * 400 ms (level 3 TX drain rate * level 3 TX duration)
+ // + 2160 mA * 500 ms (level 4 TX drain rate * level 4 TX duration)
+ // + 1440 mA * 600 ms (RX drain rate * RX duration)
+ // + 360 mA * 3000 ms (idle drain rate * idle duration)
+ // + 70 mA * 2000 ms (sleep drain rate * sleep duration)
+ // _________________
+ // = 4604000 mA-ms or 1.27888 mA-h
+ assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+ .isWithin(PRECISION).of(1.27888);
+ assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+ .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+
+ // 720 mA * 100 ms (level 0 TX drain rate * level 0 TX duration)
+ // + 1080 mA * 200 ms (level 1 TX drain rate * level 1 TX duration)
+ // + 1440 mA * 300 ms (level 2 TX drain rate * level 2 TX duration)
+ // + 1800 mA * 400 ms (level 3 TX drain rate * level 3 TX duration)
+ // + 2160 mA * 500 ms (level 4 TX drain rate * level 4 TX duration)
+ // + 1440 mA * 600 ms (RX drain rate * RX duration)
+ // _________________
+ // = 3384000 mA-ms or 0.94 mA-h
+ BatteryConsumer appsConsumer = mStatsRule.getAppsBatteryConsumer();
+ assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+ .isWithin(PRECISION).of(0.94);
+ assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+ .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+
+ // 3/4 of total packets were sent by APP_UID so 75% of total
+ UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID);
+ assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+ .isWithin(PRECISION).of(0.705);
+ assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+ .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+
+ // Rest should go to the other app
+ UidBatteryConsumer uidConsumer2 = mStatsRule.getUidBatteryConsumer(APP_UID2);
+ assertThat(uidConsumer2.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+ .isWithin(PRECISION).of(0.235);
+ assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+ .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+ }
+
+ @Test
+ public void testCounterBasedModel_multipleDefinedRat() {
+ mStatsRule.setTestPowerProfile(R.xml.power_profile_test_modem_calculator_multiactive)
+ .initMeasuredEnergyStatsLocked();
+ BatteryStatsImpl stats = mStatsRule.getBatteryStats();
+
+ // Scan for a cell
+ stats.notePhoneStateLocked(ServiceState.STATE_OUT_OF_SERVICE,
+ TelephonyManager.SIM_STATE_READY,
+ 2000, 2000);
+
+ // Found a cell
+ stats.notePhoneStateLocked(ServiceState.STATE_IN_SERVICE, TelephonyManager.SIM_STATE_READY,
+ 5000, 5000);
+
+ ArrayList<CellSignalStrength> perRatCellStrength = new ArrayList();
+ CellSignalStrength gsmSignalStrength = mock(CellSignalStrength.class);
+ when(gsmSignalStrength.getLevel()).thenReturn(
+ SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
+ perRatCellStrength.add(gsmSignalStrength);
+
+ // Note cell signal strength
+ SignalStrength signalStrength = mock(SignalStrength.class);
+ when(signalStrength.getCellSignalStrengths()).thenReturn(perRatCellStrength);
+ stats.notePhoneSignalStrengthLocked(signalStrength, 5000, 5000);
+
+ stats.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH,
+ 8_000_000_000L, APP_UID, 8000, 8000);
+
+ // Note established network
+ stats.noteNetworkInterfaceForTransports("cellular",
+ new int[]{NetworkCapabilities.TRANSPORT_CELLULAR});
+
+ // Spend some time in each signal strength level. It doesn't matter how long.
+ // The ModemActivityInfo reported level residency should be trusted over the BatteryStats
+ // timers.
+ when(gsmSignalStrength.getLevel()).thenReturn(
+ SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
+ stats.notePhoneSignalStrengthLocked(signalStrength, 8111, 8111);
+ when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_POOR);
+ stats.notePhoneSignalStrengthLocked(signalStrength, 8333, 8333);
+ when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_MODERATE);
+ stats.notePhoneSignalStrengthLocked(signalStrength, 8666, 8666);
+ when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_GOOD);
+ stats.notePhoneSignalStrengthLocked(signalStrength, 9110, 9110);
+
+ stats.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH,
+ 9_500_000_000L, APP_UID2, 9500, 9500);
+
+ when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_GREAT);
+ stats.notePhoneSignalStrengthLocked(signalStrength, 9665, 9665);
+
+ // Note application network activity
+ NetworkStats networkStats = new NetworkStats(10000, 1)
+ .addEntry(new NetworkStats.Entry("cellular", APP_UID, 0, 0,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 150, 2000, 30, 100))
+ .addEntry(new NetworkStats.Entry("cellular", APP_UID2, 0, 0,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 500, 50, 300, 10, 111));
+ mStatsRule.setNetworkStats(networkStats);
+
+ ActivityStatsTechSpecificInfo cdmaInfo = new ActivityStatsTechSpecificInfo(
+ AccessNetwork.CDMA2000, ServiceState.FREQUENCY_RANGE_UNKNOWN,
+ new int[]{10, 11, 12, 13, 14}, 15);
+ ActivityStatsTechSpecificInfo lteInfo = new ActivityStatsTechSpecificInfo(
+ AccessNetwork.EUTRAN, ServiceState.FREQUENCY_RANGE_UNKNOWN,
+ new int[]{20, 21, 22, 23, 24}, 25);
+ ActivityStatsTechSpecificInfo nrLowFreqInfo = new ActivityStatsTechSpecificInfo(
+ AccessNetwork.NGRAN, ServiceState.FREQUENCY_RANGE_LOW,
+ new int[]{30, 31, 32, 33, 34}, 35);
+ ActivityStatsTechSpecificInfo nrMidFreqInfo = new ActivityStatsTechSpecificInfo(
+ AccessNetwork.NGRAN, ServiceState.FREQUENCY_RANGE_MID,
+ new int[]{40, 41, 42, 43, 44}, 45);
+ ActivityStatsTechSpecificInfo nrHighFreqInfo = new ActivityStatsTechSpecificInfo(
+ AccessNetwork.NGRAN, ServiceState.FREQUENCY_RANGE_HIGH,
+ new int[]{50, 51, 52, 53, 54}, 55);
+ ActivityStatsTechSpecificInfo nrMmwaveFreqInfo = new ActivityStatsTechSpecificInfo(
+ AccessNetwork.NGRAN, ServiceState.FREQUENCY_RANGE_MMWAVE,
+ new int[]{60, 61, 62, 63, 64}, 65);
+
+ ActivityStatsTechSpecificInfo[] ratInfos =
+ new ActivityStatsTechSpecificInfo[]{cdmaInfo, lteInfo, nrLowFreqInfo, nrMidFreqInfo,
+ nrHighFreqInfo, nrMmwaveFreqInfo};
+
+ ModemActivityInfo mai = new ModemActivityInfo(10000, 2000, 3000, ratInfos);
+ stats.noteModemControllerActivity(mai, POWER_DATA_UNAVAILABLE, 10000, 10000,
+ mNetworkStatsManager);
+
+ mStatsRule.setTime(10_000, 10_000);
MobileRadioPowerCalculator calculator =
new MobileRadioPowerCalculator(mStatsRule.getPowerProfile());
mStatsRule.apply(BatteryUsageStatsRule.POWER_PROFILE_MODEL_ONLY, calculator);
+ BatteryConsumer deviceConsumer = mStatsRule.getDeviceBatteryConsumer();
+ // CDMA2000 [Tx0, Tx1, Tx2, Tx3, Tx4, Rx] drain * duration
+ // [720, 1080, 1440, 1800, 2160, 1440] mA . [10, 11, 12, 13, 14, 15] ms = 111600 mA-ms
+ // LTE [Tx0, Tx1, Tx2, Tx3, Tx4, Rx] drain * duration
+ // [800, 1200, 1600, 2000, 2400, 2000] mA . [20, 21, 22, 23, 24, 25] ms = 230000 mA-ms
+ // 5G Low Frequency [Tx0, Tx1, Tx2, Tx3, Tx4, Rx] drain * duration
+ // (nrFrequency="LOW" values was not defined so fall back to nrFrequency="DEFAULT")
+ // [999, 1333, 1888, 2222, 2666, 2222] mA . [30, 31, 32, 33, 34, 35] ms = 373449 mA-ms
+ // 5G Mid Frequency [Tx0, Tx1, Tx2, Tx3, Tx4, Rx] drain * duration
+ // (nrFrequency="MID" values was not defined so fall back to nrFrequency="DEFAULT")
+ // [999, 1333, 1888, 2222, 2666, 2222] mA . [40, 41, 42, 43, 44, 45] ms = 486749 mA-ms
+ // 5G High Frequency [Tx0, Tx1, Tx2, Tx3, Tx4, Rx] drain * duration
+ // [1818, 2727, 3636, 4545, 5454, 2727] mA . [50, 51, 52, 53, 54, 55] ms = 1104435 mA-ms
+ // 5G Mmwave Frequency [Tx0, Tx1, Tx2, Tx3, Tx4, Rx] drain * duration
+ // [2345, 3456, 4567, 5678, 6789, 3456] mA . [60, 61, 62, 63, 64, 65] ms = 1651520 mA-ms
+ // _________________
+ // = 3957753 mA-ms or 1.09938 mA-h active consumption
+ //
+ // Idle drain rate * idle duration
+ // 360 mA * 3000 ms = 1080000 mA-ms
+ // Sleep drain rate * sleep duration
+ // 70 mA * 2000 ms = 140000 mA-ms
+ // _________________
+ // = 5177753 mA-ms or 1.43826 mA-h total consumption
+ assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+ .isWithin(PRECISION).of(1.43826);
+ assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+ .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+
+ // 720 mA * 100 ms (level 0 TX drain rate * level 0 TX duration)
+ // + 1080 mA * 200 ms (level 1 TX drain rate * level 1 TX duration)
+ // + 1440 mA * 300 ms (level 2 TX drain rate * level 2 TX duration)
+ // + 1800 mA * 400 ms (level 3 TX drain rate * level 3 TX duration)
+ // + 2160 mA * 500 ms (level 4 TX drain rate * level 4 TX duration)
+ // + 1440 mA * 600 ms (RX drain rate * RX duration)
+ // _________________
+ // = 3384000 mA-ms or 0.94 mA-h
+ BatteryConsumer appsConsumer = mStatsRule.getAppsBatteryConsumer();
+ assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+ .isWithin(PRECISION).of(1.09938);
+ assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+ .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+
+ // 3/4 of total packets were sent by APP_UID so 75% of total
UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID);
assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
- .isWithin(PRECISION).of(0.8);
+ .isWithin(PRECISION).of(0.82453);
+ assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+ .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+
+ // Rest should go to the other app
+ UidBatteryConsumer uidConsumer2 = mStatsRule.getUidBatteryConsumer(APP_UID2);
+ assertThat(uidConsumer2.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+ .isWithin(PRECISION).of(0.27484);
assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
.isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+ }
+
+ @Test
+ public void testCounterBasedModel_legacyPowerProfile() {
+ mStatsRule.setTestPowerProfile(R.xml.power_profile_test_legacy_modem)
+ .initMeasuredEnergyStatsLocked();
+ BatteryStatsImpl stats = mStatsRule.getBatteryStats();
+
+ // Scan for a cell
+ stats.notePhoneStateLocked(ServiceState.STATE_OUT_OF_SERVICE,
+ TelephonyManager.SIM_STATE_READY,
+ 2000, 2000);
+
+ // Found a cell
+ stats.notePhoneStateLocked(ServiceState.STATE_IN_SERVICE, TelephonyManager.SIM_STATE_READY,
+ 5000, 5000);
+
+ ArrayList<CellSignalStrength> perRatCellStrength = new ArrayList();
+ CellSignalStrength gsmSignalStrength = mock(CellSignalStrength.class);
+ when(gsmSignalStrength.getLevel()).thenReturn(
+ SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
+ perRatCellStrength.add(gsmSignalStrength);
+
+ // Note cell signal strength
+ SignalStrength signalStrength = mock(SignalStrength.class);
+ when(signalStrength.getCellSignalStrengths()).thenReturn(perRatCellStrength);
+ stats.notePhoneSignalStrengthLocked(signalStrength, 5000, 5000);
+
+ stats.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH,
+ 8_000_000_000L, APP_UID, 8000, 8000);
+
+ // Note established network
+ stats.noteNetworkInterfaceForTransports("cellular",
+ new int[]{NetworkCapabilities.TRANSPORT_CELLULAR});
+
+ // Spend some time in each signal strength level. It doesn't matter how long.
+ // The ModemActivityInfo reported level residency should be trusted over the BatteryStats
+ // timers.
+ when(gsmSignalStrength.getLevel()).thenReturn(
+ SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
+ stats.notePhoneSignalStrengthLocked(signalStrength, 8111, 8111);
+ when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_POOR);
+ stats.notePhoneSignalStrengthLocked(signalStrength, 8333, 8333);
+ when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_MODERATE);
+ stats.notePhoneSignalStrengthLocked(signalStrength, 8666, 8666);
+ when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_GOOD);
+ stats.notePhoneSignalStrengthLocked(signalStrength, 9110, 9110);
+
+ stats.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH,
+ 9_500_000_000L, APP_UID2, 9500, 9500);
+
+ when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_GREAT);
+ stats.notePhoneSignalStrengthLocked(signalStrength, 9665, 9665);
+
+ // Note application network activity
+ NetworkStats networkStats = new NetworkStats(10000, 1)
+ .addEntry(new NetworkStats.Entry("cellular", APP_UID, 0, 0,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 150, 2000, 30, 100))
+ .addEntry(new NetworkStats.Entry("cellular", APP_UID2, 0, 0,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 500, 50, 300, 10, 111));
+ mStatsRule.setNetworkStats(networkStats);
+
+ ModemActivityInfo mai = new ModemActivityInfo(10000, 2000, 3000,
+ new int[]{100, 200, 300, 400, 500}, 600);
+ stats.noteModemControllerActivity(mai, POWER_DATA_UNAVAILABLE, 10000, 10000,
+ mNetworkStatsManager);
+
+ mStatsRule.setTime(10_000, 10_000);
+
+ MobileRadioPowerCalculator calculator =
+ new MobileRadioPowerCalculator(mStatsRule.getPowerProfile());
+
+ mStatsRule.apply(BatteryUsageStatsRule.POWER_PROFILE_MODEL_ONLY, calculator);
BatteryConsumer deviceConsumer = mStatsRule.getDeviceBatteryConsumer();
+ // 720 mA * 100 ms (level 0 TX drain rate * level 0 TX duration)
+ // + 1080 mA * 200 ms (level 1 TX drain rate * level 1 TX duration)
+ // + 1440 mA * 300 ms (level 2 TX drain rate * level 2 TX duration)
+ // + 1800 mA * 400 ms (level 3 TX drain rate * level 3 TX duration)
+ // + 2160 mA * 500 ms (level 4 TX drain rate * level 4 TX duration)
+ // + 1440 mA * 600 ms (RX drain rate * RX duration)
+ // + 360 mA * 3000 ms (idle drain rate * idle duration)
+ // + 70 mA * 2000 ms (sleep drain rate * sleep duration)
+ // _________________
+ // = 4604000 mA-ms or 1.27888 mA-h
assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
- .isWithin(PRECISION).of(2.2444);
+ .isWithin(PRECISION).of(1.27888);
assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
.isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+ // 720 mA * 100 ms (level 0 TX drain rate * level 0 TX duration)
+ // + 1080 mA * 200 ms (level 1 TX drain rate * level 1 TX duration)
+ // + 1440 mA * 300 ms (level 2 TX drain rate * level 2 TX duration)
+ // + 1800 mA * 400 ms (level 3 TX drain rate * level 3 TX duration)
+ // + 2160 mA * 500 ms (level 4 TX drain rate * level 4 TX duration)
+ // + 1440 mA * 600 ms (RX drain rate * RX duration)
+ // _________________
+ // = 3384000 mA-ms or 0.94 mA-h
BatteryConsumer appsConsumer = mStatsRule.getAppsBatteryConsumer();
assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
- .isWithin(PRECISION).of(0.8);
+ .isWithin(PRECISION).of(0.94);
assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
.isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+
+ // 3/4 of total packets were sent by APP_UID so 75% of total
+ UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID);
+ assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+ .isWithin(PRECISION).of(0.705);
+ assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+ .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+
+ // Rest should go to the other app
+ UidBatteryConsumer uidConsumer2 = mStatsRule.getUidBatteryConsumer(APP_UID2);
+ assertThat(uidConsumer2.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+ .isWithin(PRECISION).of(0.235);
+ assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+ .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
}
@Test
public void testTimerBasedModel_byProcessState() {
+ mStatsRule.setTestPowerProfile(R.xml.power_profile_test_legacy_modem)
+ .initMeasuredEnergyStatsLocked();
BatteryStatsImpl stats = mStatsRule.getBatteryStats();
BatteryStatsImpl.Uid uid = stats.getUidStatsLocked(APP_UID);
@@ -148,13 +472,19 @@ public class MobileRadioPowerCalculatorTest {
TelephonyManager.SIM_STATE_READY,
2000, 2000);
+ ArrayList<CellSignalStrength> perRatCellStrength = new ArrayList();
+ CellSignalStrength gsmSignalStrength = mock(CellSignalStrength.class);
+ when(gsmSignalStrength.getLevel()).thenReturn(
+ SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
+ perRatCellStrength.add(gsmSignalStrength);
+
// Found a cell
stats.notePhoneStateLocked(ServiceState.STATE_IN_SERVICE, TelephonyManager.SIM_STATE_READY,
5000, 5000);
// Note cell signal strength
SignalStrength signalStrength = mock(SignalStrength.class);
- when(signalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_MODERATE);
+ when(signalStrength.getCellSignalStrengths()).thenReturn(perRatCellStrength);
stats.notePhoneSignalStrengthLocked(signalStrength, 5000, 5000);
stats.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH,
@@ -164,10 +494,25 @@ public class MobileRadioPowerCalculatorTest {
stats.noteNetworkInterfaceForTransports("cellular",
new int[]{NetworkCapabilities.TRANSPORT_CELLULAR});
+ // Spend some time in each signal strength level. It doesn't matter how long.
+ // The ModemActivityInfo reported level residency should be trusted over the BatteryStats
+ // timers.
+ when(gsmSignalStrength.getLevel()).thenReturn(
+ SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
+ stats.notePhoneSignalStrengthLocked(signalStrength, 8111, 8111);
+ when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_POOR);
+ stats.notePhoneSignalStrengthLocked(signalStrength, 8333, 8333);
+ when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_MODERATE);
+ stats.notePhoneSignalStrengthLocked(signalStrength, 8666, 8666);
+ when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_GOOD);
+ stats.notePhoneSignalStrengthLocked(signalStrength, 9110, 9110);
+ when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_GREAT);
+ stats.notePhoneSignalStrengthLocked(signalStrength, 9665, 9665);
+
// Note application network activity
mStatsRule.setNetworkStats(new NetworkStats(10000, 1)
.addEntry(new NetworkStats.Entry("cellular", APP_UID, 0, 0,
- METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 100, 2000, 20, 100)));
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 100, 2000, 20, 100)));
stats.noteModemControllerActivity(null, POWER_DATA_UNAVAILABLE, 10000, 10000,
mNetworkStatsManager);
@@ -177,7 +522,7 @@ public class MobileRadioPowerCalculatorTest {
mStatsRule.setNetworkStats(new NetworkStats(12000, 1)
.addEntry(new NetworkStats.Entry("cellular", APP_UID, 0, 0,
- METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 250, 2000, 80, 200)));
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 250, 2000, 80, 200)));
stats.noteModemControllerActivity(null, POWER_DATA_UNAVAILABLE, 12000, 12000,
mNetworkStatsManager);
@@ -199,6 +544,8 @@ public class MobileRadioPowerCalculatorTest {
MobileRadioPowerCalculator calculator =
new MobileRadioPowerCalculator(mStatsRule.getPowerProfile());
+ mStatsRule.setTime(12_000, 12_000);
+
mStatsRule.apply(new BatteryUsageStatsQuery.Builder()
.powerProfileModeledOnly()
.includePowerModels()
@@ -217,6 +564,10 @@ public class MobileRadioPowerCalculatorTest {
BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE);
+
+ assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+ .isWithin(PRECISION).of(1.6);
+
assertThat(uidConsumer.getConsumedPower(foreground)).isWithin(PRECISION).of(1.2);
assertThat(uidConsumer.getConsumedPower(background)).isWithin(PRECISION).of(0.4);
assertThat(uidConsumer.getConsumedPower(fgs)).isWithin(PRECISION).of(0);
@@ -224,6 +575,8 @@ public class MobileRadioPowerCalculatorTest {
@Test
public void testMeasuredEnergyBasedModel() {
+ mStatsRule.setTestPowerProfile(R.xml.power_profile_test_legacy_modem)
+ .initMeasuredEnergyStatsLocked();
BatteryStatsImpl stats = mStatsRule.getBatteryStats();
// Scan for a cell
@@ -264,12 +617,6 @@ public class MobileRadioPowerCalculatorTest {
mStatsRule.apply(calculator);
- UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID);
- assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
- .isWithin(PRECISION).of(1.53934);
- assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
- .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
-
BatteryConsumer deviceConsumer = mStatsRule.getDeviceBatteryConsumer();
// 10_000_000 micro-Coulomb * 1/1000 milli/micro * 1/3600 hour/second = 2.77778 mAh
assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
@@ -282,10 +629,18 @@ public class MobileRadioPowerCalculatorTest {
.isWithin(PRECISION).of(1.53934);
assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
.isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
+
+ UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID);
+ assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+ .isWithin(PRECISION).of(1.53934);
+ assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+ .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
}
@Test
public void testMeasuredEnergyBasedModel_byProcessState() {
+ mStatsRule.setTestPowerProfile(R.xml.power_profile_test_legacy_modem)
+ .initMeasuredEnergyStatsLocked();
BatteryStatsImpl stats = mStatsRule.getBatteryStats();
BatteryStatsImpl.Uid uid = stats.getUidStatsLocked(APP_UID);
@@ -317,7 +672,7 @@ public class MobileRadioPowerCalculatorTest {
// Note application network activity
mStatsRule.setNetworkStats(new NetworkStats(10000, 1)
.addEntry(new NetworkStats.Entry("cellular", APP_UID, 0, 0,
- METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 100, 2000, 20, 100)));
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 100, 2000, 20, 100)));
stats.noteModemControllerActivity(null, 10_000_000, 10000, 10000, mNetworkStatsManager);
@@ -326,7 +681,7 @@ public class MobileRadioPowerCalculatorTest {
mStatsRule.setNetworkStats(new NetworkStats(12000, 1)
.addEntry(new NetworkStats.Entry("cellular", APP_UID, 0, 0,
- METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 250, 2000, 80, 200)));
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 250, 2000, 80, 200)));
stats.noteModemControllerActivity(null, 15_000_000, 12000, 12000, mNetworkStatsManager);
diff --git a/services/tests/servicestests/test-apps/PackageParserApp/AndroidManifestApp4.xml b/services/tests/servicestests/test-apps/PackageParserApp/AndroidManifestApp4.xml
index 4dcb442ebee2..5451735b2e86 100644
--- a/services/tests/servicestests/test-apps/PackageParserApp/AndroidManifestApp4.xml
+++ b/services/tests/servicestests/test-apps/PackageParserApp/AndroidManifestApp4.xml
@@ -32,7 +32,7 @@
<activity android:name="com.android.servicestests.apps.packageparserapp.MyActivity"
android:exported="true"
- android:targetDisplayCategory="automotive">
+ android:requiredDisplayCategory="automotive">
<property android:name="android.cts.PROPERTY_ACTIVITY" android:value="@integer/integer_property" />
<property android:name="android.cts.PROPERTY_COMPONENT" android:value="@integer/integer_property" />
<property android:name="android.cts.PROPERTY_STRING" android:value="koala activity" />
diff --git a/services/tests/servicestests/test-apps/PackageParserApp/AndroidManifestApp6.xml b/services/tests/servicestests/test-apps/PackageParserApp/AndroidManifestApp6.xml
index 8e694e1a1eb0..601479d82aff 100644
--- a/services/tests/servicestests/test-apps/PackageParserApp/AndroidManifestApp6.xml
+++ b/services/tests/servicestests/test-apps/PackageParserApp/AndroidManifestApp6.xml
@@ -21,7 +21,7 @@
<application>
<activity android:name="com.android.servicestests.apps.packageparserapp.MyActivity"
android:exported="true"
- android:targetDisplayCategory="$automotive">
+ android:requiredDisplayCategory="$automotive">
</activity>
</application>
</manifest>
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java
index a03a1b4226cd..ebcb498adbe8 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java
@@ -234,6 +234,37 @@ public class SnoozeHelperTest extends UiServiceTestCase {
}
@Test
+ public void testScheduleRepostsForLongTagPersistedNotification() throws Exception {
+ String longTag = "A".repeat(66000);
+ NotificationRecord r = getNotificationRecord("pkg", 1, longTag, UserHandle.SYSTEM);
+ mSnoozeHelper.snooze(r, 0);
+
+ // We store the full key in temp storage.
+ ArgumentCaptor<PendingIntent> captor = ArgumentCaptor.forClass(PendingIntent.class);
+ verify(mAm).setExactAndAllowWhileIdle(anyInt(), anyLong(), captor.capture());
+ assertEquals(66010, captor.getValue().getIntent().getStringExtra(EXTRA_KEY).length());
+
+ TypedXmlSerializer serializer = Xml.newFastSerializer();
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ serializer.setOutput(new BufferedOutputStream(baos), "utf-8");
+ serializer.startDocument(null, true);
+ mSnoozeHelper.writeXml(serializer);
+ serializer.endDocument();
+ serializer.flush();
+
+ TypedXmlPullParser parser = Xml.newFastPullParser();
+ parser.setInput(new BufferedInputStream(
+ new ByteArrayInputStream(baos.toByteArray())), "utf-8");
+ mSnoozeHelper.readXml(parser, 4);
+
+ mSnoozeHelper.scheduleRepostsForPersistedNotifications(5);
+
+ // We trim the key in persistent storage.
+ verify(mAm, times(2)).setExactAndAllowWhileIdle(anyInt(), anyLong(), captor.capture());
+ assertEquals(1000, captor.getValue().getIntent().getStringExtra(EXTRA_KEY).length());
+ }
+
+ @Test
public void testSnoozeForTime() throws Exception {
NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM);
mSnoozeHelper.snooze(r, 1000);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
index 85c49753f240..4e001fe06fb8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
@@ -106,7 +106,7 @@ public class ActivityMetricsLaunchObserverTests extends WindowTestsBase {
mTransition.mParticipants.add(mTopActivity);
mTopActivity.mTransitionController.moveToCollecting(mTransition);
// becomes invisible when covered by mTopActivity
- mTrampolineActivity.mVisibleRequested = false;
+ mTrampolineActivity.setVisibleRequested(false);
}
private <T> T verifyAsync(T mock) {
@@ -235,7 +235,7 @@ public class ActivityMetricsLaunchObserverTests extends WindowTestsBase {
public void testOnActivityLaunchCancelled_hasDrawn() {
onActivityLaunched(mTopActivity);
- mTopActivity.mVisibleRequested = true;
+ mTopActivity.setVisibleRequested(true);
doReturn(true).when(mTopActivity).isReportedDrawn();
// Cannot time already-visible activities.
@@ -258,7 +258,7 @@ public class ActivityMetricsLaunchObserverTests extends WindowTestsBase {
notifyActivityLaunching(noDrawnActivity.intent);
notifyAndVerifyActivityLaunched(noDrawnActivity);
- noDrawnActivity.mVisibleRequested = false;
+ noDrawnActivity.setVisibleRequested(false);
mActivityMetricsLogger.notifyVisibilityChanged(noDrawnActivity);
verifyAsync(mLaunchObserver).onActivityLaunchCancelled(eqLastStartedId(noDrawnActivity));
@@ -286,7 +286,7 @@ public class ActivityMetricsLaunchObserverTests extends WindowTestsBase {
clearInvocations(mLaunchObserver);
mLaunchTopByTrampoline = true;
- mTopActivity.mVisibleRequested = false;
+ mTopActivity.setVisibleRequested(false);
notifyActivityLaunching(mTopActivity.intent);
// It should schedule a message with UNKNOWN_VISIBILITY_CHECK_DELAY_MS to check whether
// the launch event is still valid.
@@ -314,7 +314,7 @@ public class ActivityMetricsLaunchObserverTests extends WindowTestsBase {
// Create an invisible event that should be cancelled after the next event starts.
final ActivityRecord prev = new ActivityBuilder(mAtm).setCreateTask(true).build();
onActivityLaunched(prev);
- prev.mVisibleRequested = false;
+ prev.setVisibleRequested(false);
mActivityOptions = ActivityOptions.makeBasic();
mActivityOptions.setSourceInfo(SourceInfo.TYPE_LAUNCHER, SystemClock.uptimeMillis() - 10);
@@ -561,7 +561,7 @@ public class ActivityMetricsLaunchObserverTests extends WindowTestsBase {
@Test
public void testConsecutiveLaunchWithDifferentWindowingMode() {
mTopActivity.setWindowingMode(WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW);
- mTrampolineActivity.mVisibleRequested = true;
+ mTrampolineActivity.setVisibleRequested(true);
onActivityLaunched(mTrampolineActivity);
mActivityMetricsLogger.notifyActivityLaunching(mTopActivity.intent,
mTrampolineActivity /* caller */, mTrampolineActivity.getUid());
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index a410eedf4c29..f983fa9b49e3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -917,7 +917,7 @@ public class ActivityRecordTests extends WindowTestsBase {
// Prepare the activity record to be ready for immediate removal. It should be invisible and
// have no process. Otherwise, request to finish it will send a message to client first.
activity.setState(STOPPED, "test");
- activity.mVisibleRequested = false;
+ activity.setVisibleRequested(false);
activity.nowVisible = false;
// Set process to 'null' to allow immediate removal, but don't call mActivity.setProcess() -
// this will cause NPE when updating task's process.
@@ -927,7 +927,7 @@ public class ActivityRecordTests extends WindowTestsBase {
// next activity reports idle to destroy it.
final ActivityRecord topActivity = new ActivityBuilder(mAtm)
.setTask(activity.getTask()).build();
- topActivity.mVisibleRequested = true;
+ topActivity.setVisibleRequested(true);
topActivity.nowVisible = true;
topActivity.setState(RESUMED, "test");
@@ -1082,7 +1082,7 @@ public class ActivityRecordTests extends WindowTestsBase {
final ActivityRecord activity = createActivityWithTask();
clearInvocations(activity.mDisplayContent);
activity.finishing = false;
- activity.mVisibleRequested = true;
+ activity.setVisibleRequested(true);
activity.setState(RESUMED, "test");
activity.finishIfPossible("test", false /* oomAdj */);
@@ -1099,7 +1099,7 @@ public class ActivityRecordTests extends WindowTestsBase {
final ActivityRecord activity = createActivityWithTask();
clearInvocations(activity.mDisplayContent);
activity.finishing = false;
- activity.mVisibleRequested = true;
+ activity.setVisibleRequested(true);
activity.setState(PAUSED, "test");
activity.finishIfPossible("test", false /* oomAdj */);
@@ -1118,7 +1118,7 @@ public class ActivityRecordTests extends WindowTestsBase {
// Put an activity on top of test activity to make it invisible and prevent us from
// accidentally resuming the topmost one again.
new ActivityBuilder(mAtm).build();
- activity.mVisibleRequested = false;
+ activity.setVisibleRequested(false);
activity.setState(STOPPED, "test");
activity.finishIfPossible("test", false /* oomAdj */);
@@ -1136,7 +1136,7 @@ public class ActivityRecordTests extends WindowTestsBase {
final TestTransitionPlayer testPlayer = registerTestTransitionPlayer();
final ActivityRecord activity = createActivityWithTask();
activity.finishing = false;
- activity.mVisibleRequested = true;
+ activity.setVisibleRequested(true);
activity.setState(RESUMED, "test");
activity.finishIfPossible("test", false /* oomAdj */);
@@ -1273,7 +1273,7 @@ public class ActivityRecordTests extends WindowTestsBase {
final ActivityRecord currentTop = createActivityWithTask();
final Task task = currentTop.getTask();
- currentTop.mVisibleRequested = currentTop.nowVisible = true;
+ currentTop.setVisibleRequested(currentTop.nowVisible = true);
// Simulates that {@code currentTop} starts an existing activity from background (so its
// state is stopped) and the starting flow just goes to place it at top.
@@ -1300,7 +1300,7 @@ public class ActivityRecordTests extends WindowTestsBase {
final ActivityRecord bottomActivity = createActivityWithTask();
final ActivityRecord topActivity = new ActivityBuilder(mAtm)
.setTask(bottomActivity.getTask()).build();
- topActivity.mVisibleRequested = true;
+ topActivity.setVisibleRequested(true);
// simulating bottomActivity as a trampoline activity.
bottomActivity.setState(RESUMED, "test");
bottomActivity.finishIfPossible("test", false);
@@ -1316,13 +1316,13 @@ public class ActivityRecordTests extends WindowTestsBase {
final ActivityRecord activity = createActivityWithTask();
final ActivityRecord topActivity = new ActivityBuilder(mAtm)
.setTask(activity.getTask()).build();
- topActivity.mVisibleRequested = true;
+ topActivity.setVisibleRequested(true);
topActivity.nowVisible = true;
topActivity.finishing = true;
topActivity.setState(PAUSED, "true");
// Mark the bottom activity as not visible, so that we will wait for it before removing
// the top one.
- activity.mVisibleRequested = false;
+ activity.setVisibleRequested(false);
activity.nowVisible = false;
activity.setState(STOPPED, "test");
@@ -1346,13 +1346,13 @@ public class ActivityRecordTests extends WindowTestsBase {
final ActivityRecord topActivity = createActivityWithTask();
mDisplayContent.setIsSleeping(true);
doReturn(true).when(activity).shouldBeVisible();
- topActivity.mVisibleRequested = false;
+ topActivity.setVisibleRequested(false);
topActivity.nowVisible = false;
topActivity.finishing = true;
topActivity.setState(STOPPED, "true");
// Mark the activity behind (on a separate task) as not visible
- activity.mVisibleRequested = false;
+ activity.setVisibleRequested(false);
activity.nowVisible = false;
activity.setState(STOPPED, "test");
@@ -1370,13 +1370,13 @@ public class ActivityRecordTests extends WindowTestsBase {
final ActivityRecord activity = createActivityWithTask();
final ActivityRecord topActivity = new ActivityBuilder(mAtm)
.setTask(activity.getTask()).build();
- topActivity.mVisibleRequested = false;
+ topActivity.setVisibleRequested(false);
topActivity.nowVisible = false;
topActivity.finishing = true;
topActivity.setState(STOPPED, "true");
// Mark the bottom activity as not visible, so that we would wait for it before removing
// the top one.
- activity.mVisibleRequested = false;
+ activity.setVisibleRequested(false);
activity.nowVisible = false;
activity.setState(STOPPED, "test");
@@ -1394,12 +1394,12 @@ public class ActivityRecordTests extends WindowTestsBase {
final ActivityRecord activity = createActivityWithTask();
final ActivityRecord topActivity = new ActivityBuilder(mAtm)
.setTask(activity.getTask()).build();
- topActivity.mVisibleRequested = true;
+ topActivity.setVisibleRequested(true);
topActivity.nowVisible = true;
topActivity.finishing = true;
topActivity.setState(PAUSED, "true");
// Mark the bottom activity as already visible, so that there is no need to wait for it.
- activity.mVisibleRequested = true;
+ activity.setVisibleRequested(true);
activity.nowVisible = true;
activity.setState(RESUMED, "test");
@@ -1417,12 +1417,12 @@ public class ActivityRecordTests extends WindowTestsBase {
final ActivityRecord activity = createActivityWithTask();
final ActivityRecord topActivity = new ActivityBuilder(mAtm)
.setTask(activity.getTask()).build();
- topActivity.mVisibleRequested = false;
+ topActivity.setVisibleRequested(false);
topActivity.nowVisible = false;
topActivity.finishing = true;
topActivity.setState(STOPPED, "true");
// Mark the bottom activity as already visible, so that there is no need to wait for it.
- activity.mVisibleRequested = true;
+ activity.setVisibleRequested(true);
activity.nowVisible = true;
activity.setState(RESUMED, "test");
@@ -1440,12 +1440,12 @@ public class ActivityRecordTests extends WindowTestsBase {
final ActivityRecord activity = createActivityWithTask();
final ActivityRecord topActivity = new ActivityBuilder(mAtm)
.setTask(activity.getTask()).build();
- topActivity.mVisibleRequested = true;
+ topActivity.setVisibleRequested(true);
topActivity.nowVisible = true;
topActivity.finishing = true;
topActivity.setState(PAUSED, "true");
// Mark the bottom activity as already visible, so that there is no need to wait for it.
- activity.mVisibleRequested = true;
+ activity.setVisibleRequested(true);
activity.nowVisible = true;
activity.setState(RESUMED, "test");
@@ -1454,7 +1454,7 @@ public class ActivityRecordTests extends WindowTestsBase {
final Task stack = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
final ActivityRecord focusedActivity = stack.getTopMostActivity();
focusedActivity.nowVisible = true;
- focusedActivity.mVisibleRequested = true;
+ focusedActivity.setVisibleRequested(true);
focusedActivity.setState(RESUMED, "test");
stack.setResumedActivity(focusedActivity, "test");
@@ -1476,7 +1476,7 @@ public class ActivityRecordTests extends WindowTestsBase {
int displayId = activity.getDisplayId();
doReturn(true).when(keyguardController).isKeyguardLocked(eq(displayId));
final ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(task).build();
- topActivity.mVisibleRequested = true;
+ topActivity.setVisibleRequested(true);
topActivity.nowVisible = true;
topActivity.setState(RESUMED, "true");
doCallRealMethod().when(mRootWindowContainer).ensureActivitiesVisible(
@@ -1515,12 +1515,12 @@ public class ActivityRecordTests extends WindowTestsBase {
final ActivityRecord activity = createActivityWithTask();
final Task task = activity.getTask();
final ActivityRecord firstActivity = new ActivityBuilder(mAtm).setTask(task).build();
- firstActivity.mVisibleRequested = false;
+ firstActivity.setVisibleRequested(false);
firstActivity.nowVisible = false;
firstActivity.setState(STOPPED, "test");
final ActivityRecord secondActivity = new ActivityBuilder(mAtm).setTask(task).build();
- secondActivity.mVisibleRequested = true;
+ secondActivity.setVisibleRequested(true);
secondActivity.nowVisible = true;
secondActivity.setState(secondActivityState, "test");
@@ -1530,7 +1530,7 @@ public class ActivityRecordTests extends WindowTestsBase {
} else {
translucentActivity = new ActivityBuilder(mAtm).setTask(task).build();
}
- translucentActivity.mVisibleRequested = true;
+ translucentActivity.setVisibleRequested(true);
translucentActivity.nowVisible = true;
translucentActivity.setState(RESUMED, "test");
@@ -1546,7 +1546,7 @@ public class ActivityRecordTests extends WindowTestsBase {
// Finish the first activity
firstActivity.finishing = true;
- firstActivity.mVisibleRequested = true;
+ firstActivity.setVisibleRequested(true);
firstActivity.completeFinishing("test");
verify(firstActivity.mDisplayContent, times(2)).ensureActivitiesVisible(null /* starting */,
0 /* configChanges */ , false /* preserveWindows */,
@@ -1614,7 +1614,7 @@ public class ActivityRecordTests extends WindowTestsBase {
}, true /* traverseTopToBottom */);
activity.setState(STARTED, "test");
activity.finishing = true;
- activity.mVisibleRequested = true;
+ activity.setVisibleRequested(true);
// Try to finish the last activity above the home stack.
activity.completeFinishing("test");
@@ -1909,7 +1909,7 @@ public class ActivityRecordTests extends WindowTestsBase {
// Simulate that the activity requests the same orientation as display.
activity.setOrientation(display.getConfiguration().orientation);
// Skip the real freezing.
- activity.mVisibleRequested = false;
+ activity.setVisibleRequested(false);
clearInvocations(activity);
activity.onCancelFixedRotationTransform(originalRotation);
// The implementation of cancellation must be executed.
@@ -2535,7 +2535,7 @@ public class ActivityRecordTests extends WindowTestsBase {
activity.setOccludesParent(true);
activity.setVisible(false);
- activity.mVisibleRequested = false;
+ activity.setVisibleRequested(false);
// Can not specify orientation if app isn't visible even though it occludes parent.
assertEquals(SCREEN_ORIENTATION_UNSET, activity.getOrientation());
// Can specify orientation if the current orientation candidate is orientation behind.
@@ -2910,7 +2910,7 @@ public class ActivityRecordTests extends WindowTestsBase {
task.addChild(taskFragment2, POSITION_TOP);
final ActivityRecord activity2 = new ActivityBuilder(mAtm)
.setResizeMode(ActivityInfo.RESIZE_MODE_UNRESIZEABLE).build();
- activity2.mVisibleRequested = true;
+ activity2.setVisibleRequested(true);
taskFragment2.addChild(activity2);
assertTrue(activity2.isResizeable());
activity1.reparent(taskFragment1, POSITION_TOP);
@@ -3055,7 +3055,7 @@ public class ActivityRecordTests extends WindowTestsBase {
.setCreateTask(true).build();
// By default, activity is visible.
assertTrue(activity.isVisible());
- assertTrue(activity.mVisibleRequested);
+ assertTrue(activity.isVisibleRequested());
assertTrue(activity.mDisplayContent.mOpeningApps.contains(activity));
assertFalse(activity.mDisplayContent.mClosingApps.contains(activity));
@@ -3064,7 +3064,7 @@ public class ActivityRecordTests extends WindowTestsBase {
// until we verify no logic relies on this behavior, we'll keep this as is.
activity.setVisibility(true);
assertTrue(activity.isVisible());
- assertTrue(activity.mVisibleRequested);
+ assertTrue(activity.isVisibleRequested());
assertTrue(activity.mDisplayContent.mOpeningApps.contains(activity));
assertFalse(activity.mDisplayContent.mClosingApps.contains(activity));
}
@@ -3075,7 +3075,7 @@ public class ActivityRecordTests extends WindowTestsBase {
.setCreateTask(true).build();
// By default, activity is visible.
assertTrue(activity.isVisible());
- assertTrue(activity.mVisibleRequested);
+ assertTrue(activity.isVisibleRequested());
assertTrue(activity.mDisplayContent.mOpeningApps.contains(activity));
assertFalse(activity.mDisplayContent.mClosingApps.contains(activity));
@@ -3083,7 +3083,7 @@ public class ActivityRecordTests extends WindowTestsBase {
// animation should be applied on this activity.
activity.setVisibility(false);
assertTrue(activity.isVisible());
- assertFalse(activity.mVisibleRequested);
+ assertFalse(activity.isVisibleRequested());
assertFalse(activity.mDisplayContent.mOpeningApps.contains(activity));
assertTrue(activity.mDisplayContent.mClosingApps.contains(activity));
}
@@ -3095,7 +3095,7 @@ public class ActivityRecordTests extends WindowTestsBase {
// Activiby is invisible. However ATMS requests it to become visible, since this is a top
// activity.
assertFalse(activity.isVisible());
- assertTrue(activity.mVisibleRequested);
+ assertTrue(activity.isVisibleRequested());
assertTrue(activity.mDisplayContent.mOpeningApps.contains(activity));
assertFalse(activity.mDisplayContent.mClosingApps.contains(activity));
@@ -3103,7 +3103,7 @@ public class ActivityRecordTests extends WindowTestsBase {
// animation should be applied on this activity.
activity.setVisibility(true);
assertFalse(activity.isVisible());
- assertTrue(activity.mVisibleRequested);
+ assertTrue(activity.isVisibleRequested());
assertTrue(activity.mDisplayContent.mOpeningApps.contains(activity));
assertFalse(activity.mDisplayContent.mClosingApps.contains(activity));
@@ -3126,7 +3126,7 @@ public class ActivityRecordTests extends WindowTestsBase {
// Activiby is invisible. However ATMS requests it to become visible, since this is a top
// activity.
assertFalse(activity.isVisible());
- assertTrue(activity.mVisibleRequested);
+ assertTrue(activity.isVisibleRequested());
assertTrue(activity.mDisplayContent.mOpeningApps.contains(activity));
assertFalse(activity.mDisplayContent.mClosingApps.contains(activity));
@@ -3134,7 +3134,7 @@ public class ActivityRecordTests extends WindowTestsBase {
// transition should be applied on this activity.
activity.setVisibility(false);
assertFalse(activity.isVisible());
- assertFalse(activity.mVisibleRequested);
+ assertFalse(activity.isVisibleRequested());
assertFalse(activity.mDisplayContent.mOpeningApps.contains(activity));
assertFalse(activity.mDisplayContent.mClosingApps.contains(activity));
}
@@ -3549,12 +3549,12 @@ public class ActivityRecordTests extends WindowTestsBase {
activity.reparent(taskFragment, POSITION_TOP);
// Ensure the activity visibility is updated even it is not shown to current user.
- activity.mVisibleRequested = true;
+ activity.setVisibleRequested(true);
doReturn(false).when(activity).showToCurrentUser();
spyOn(taskFragment);
doReturn(false).when(taskFragment).shouldBeVisible(any());
display.ensureActivitiesVisible(null, 0, false, false);
- assertFalse(activity.mVisibleRequested);
+ assertFalse(activity.isVisibleRequested());
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index 0743ef471d81..ee31748e24c7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -514,7 +514,9 @@ public class ActivityStarterTests extends WindowTestsBase {
.setCreateActivity(true)
.build()
.getTopMostActivity();
- splitPrimaryActivity.mVisibleRequested = splitSecondActivity.mVisibleRequested = true;
+
+ splitPrimaryActivity.setVisibleRequested(true);
+ splitSecondActivity.setVisibleRequested(true);
assertEquals(splitOrg.mPrimary, splitPrimaryActivity.getRootTask());
assertEquals(splitOrg.mSecondary, splitSecondActivity.getRootTask());
@@ -527,7 +529,7 @@ public class ActivityStarterTests extends WindowTestsBase {
.setCreateActivity(true).build().getTopMostActivity();
final ActivityRecord translucentActivity = new TaskBuilder(mSupervisor)
.setCreateActivity(true).build().getTopMostActivity();
- assertTrue(activity.mVisibleRequested);
+ assertTrue(activity.isVisibleRequested());
final ActivityStarter starter = prepareStarter(FLAG_ACTIVITY_NEW_TASK,
false /* mockGetRootTask */);
@@ -1050,7 +1052,7 @@ public class ActivityStarterTests extends WindowTestsBase {
ACTIVITY_TYPE_STANDARD, false /* onTop */));
// Activity should start invisible since we are bringing it to front.
singleTaskActivity.setVisible(false);
- singleTaskActivity.mVisibleRequested = false;
+ singleTaskActivity.setVisibleRequested(false);
// Create another activity on top of the secondary display.
final Task topStack = secondaryTaskContainer.createRootTask(WINDOWING_MODE_FULLSCREEN,
@@ -1268,7 +1270,7 @@ public class ActivityStarterTests extends WindowTestsBase {
final ActivityStarter starter = prepareStarter(0 /* flags */);
final ActivityRecord target = new ActivityBuilder(mAtm).setCreateTask(true).build();
starter.mStartActivity = target;
- target.mVisibleRequested = false;
+ target.setVisibleRequested(false);
target.setTurnScreenOn(true);
// Assume the flag was consumed by relayout.
target.setCurrentLaunchCanTurnScreenOn(false);
@@ -1589,10 +1591,10 @@ public class ActivityStarterTests extends WindowTestsBase {
final ActivityRecord activityTop = new ActivityBuilder(mAtm).setTask(task).build();
activityBot.setVisible(false);
- activityBot.mVisibleRequested = false;
+ activityBot.setVisibleRequested(false);
assertTrue(activityTop.isVisible());
- assertTrue(activityTop.mVisibleRequested);
+ assertTrue(activityTop.isVisibleRequested());
final ActivityStarter starter = prepareStarter(FLAG_ACTIVITY_REORDER_TO_FRONT
| FLAG_ACTIVITY_NEW_TASK, false /* mockGetRootTask */);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
index 2fccd64bceca..368b750967c0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
@@ -344,7 +344,7 @@ public class ActivityTaskManagerServiceTests extends WindowTestsBase {
// Assume the activity is finishing and hidden because it was crashed.
activity.finishing = true;
- activity.mVisibleRequested = false;
+ activity.setVisibleRequested(false);
activity.setVisible(false);
activity.getTask().setPausingActivity(activity);
homeActivity.setState(PAUSED, "test");
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
index fb94147c5e49..c73237efa93e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
@@ -122,7 +122,7 @@ public class AppTransitionControllerTest extends WindowTestsBase {
final ActivityRecord top = createActivityRecord(task);
top.setState(ActivityRecord.State.RESUMED, "test");
behind.setState(ActivityRecord.State.STARTED, "test");
- behind.mVisibleRequested = true;
+ behind.setVisibleRequested(true);
task.removeActivities("test", false /* excludingTaskOverlay */);
assertFalse(mDisplayContent.mAppTransition.isReady());
@@ -294,7 +294,7 @@ public class AppTransitionControllerTest extends WindowTestsBase {
final ActivityRecord activity2 = createActivityRecord(mDisplayContent);
activity2.setVisible(false);
- activity2.mVisibleRequested = false;
+ activity2.setVisibleRequested(false);
final ArraySet<ActivityRecord> opening = new ArraySet<>();
opening.add(activity1);
@@ -319,12 +319,12 @@ public class AppTransitionControllerTest extends WindowTestsBase {
// +- [Task2] - [ActivityRecord2] (opening, visible)
final ActivityRecord activity1 = createActivityRecord(mDisplayContent);
activity1.setVisible(true);
- activity1.mVisibleRequested = true;
+ activity1.setVisibleRequested(true);
activity1.mRequestForceTransition = true;
final ActivityRecord activity2 = createActivityRecord(mDisplayContent);
activity2.setVisible(false);
- activity2.mVisibleRequested = false;
+ activity2.setVisibleRequested(false);
activity2.mRequestForceTransition = true;
final ArraySet<ActivityRecord> opening = new ArraySet<>();
@@ -391,7 +391,7 @@ public class AppTransitionControllerTest extends WindowTestsBase {
final ActivityRecord activity2 = createActivityRecord(mDisplayContent);
activity2.setVisible(false);
- activity2.mVisibleRequested = false;
+ activity2.setVisibleRequested(false);
attrs.setTitle("AppWindow2");
final TestWindowState appWindow2 = createWindowState(attrs, activity2);
appWindow2.mWillReplaceWindow = true;
@@ -424,17 +424,17 @@ public class AppTransitionControllerTest extends WindowTestsBase {
// +- [ActivityRecord4] (invisible)
final ActivityRecord activity1 = createActivityRecord(mDisplayContent);
activity1.setVisible(false);
- activity1.mVisibleRequested = true;
+ activity1.setVisibleRequested(true);
final ActivityRecord activity2 = createActivityRecord(mDisplayContent,
activity1.getTask());
activity2.setVisible(false);
- activity2.mVisibleRequested = false;
+ activity2.setVisibleRequested(false);
final ActivityRecord activity3 = createActivityRecord(mDisplayContent);
final ActivityRecord activity4 = createActivityRecord(mDisplayContent,
activity3.getTask());
activity4.setVisible(false);
- activity4.mVisibleRequested = false;
+ activity4.setVisibleRequested(false);
final ArraySet<ActivityRecord> opening = new ArraySet<>();
opening.add(activity1);
@@ -459,7 +459,7 @@ public class AppTransitionControllerTest extends WindowTestsBase {
// +- [ActivityRecord2] (closing, visible)
final ActivityRecord activity1 = createActivityRecord(mDisplayContent);
activity1.setVisible(false);
- activity1.mVisibleRequested = true;
+ activity1.setVisibleRequested(true);
final ActivityRecord activity2 = createActivityRecord(mDisplayContent,
activity1.getTask());
@@ -490,7 +490,7 @@ public class AppTransitionControllerTest extends WindowTestsBase {
final ActivityRecord activity1 = createActivityRecord(mDisplayContent);
activity1.setVisible(false);
- activity1.mVisibleRequested = true;
+ activity1.setVisibleRequested(true);
activity1.setOccludesParent(false);
final ActivityRecord activity2 = createActivityRecord(mDisplayContent,
@@ -528,13 +528,13 @@ public class AppTransitionControllerTest extends WindowTestsBase {
final ActivityRecord activity1 = createActivityRecord(mDisplayContent);
activity1.setVisible(false);
- activity1.mVisibleRequested = true;
+ activity1.setVisibleRequested(true);
activity1.setOccludesParent(false);
final ActivityRecord activity2 = createActivityRecord(mDisplayContent,
activity1.getTask());
activity2.setVisible(false);
- activity2.mVisibleRequested = true;
+ activity2.setVisibleRequested(true);
final ActivityRecord activity3 = createActivityRecord(mDisplayContent);
activity3.setOccludesParent(false);
@@ -567,7 +567,7 @@ public class AppTransitionControllerTest extends WindowTestsBase {
final Task parentTask = createTask(mDisplayContent);
final ActivityRecord activity1 = createActivityRecordWithParentTask(parentTask);
activity1.setVisible(false);
- activity1.mVisibleRequested = true;
+ activity1.setVisibleRequested(true);
final ActivityRecord activity2 = createActivityRecordWithParentTask(parentTask);
final ArraySet<ActivityRecord> opening = new ArraySet<>();
@@ -600,10 +600,10 @@ public class AppTransitionControllerTest extends WindowTestsBase {
splitRoot1.setAdjacentTaskFragment(splitRoot2);
final ActivityRecord activity1 = createActivityRecordWithParentTask(splitRoot1);
activity1.setVisible(false);
- activity1.mVisibleRequested = true;
+ activity1.setVisibleRequested(true);
final ActivityRecord activity2 = createActivityRecordWithParentTask(splitRoot2);
activity2.setVisible(false);
- activity2.mVisibleRequested = true;
+ activity2.setVisibleRequested(true);
final ArraySet<ActivityRecord> opening = new ArraySet<>();
opening.add(activity1);
@@ -625,12 +625,12 @@ public class AppTransitionControllerTest extends WindowTestsBase {
final TaskFragment taskFragment1 = createTaskFragmentWithActivity(parentTask);
final ActivityRecord activity1 = taskFragment1.getTopMostActivity();
activity1.setVisible(false);
- activity1.mVisibleRequested = true;
+ activity1.setVisibleRequested(true);
final TaskFragment taskFragment2 = createTaskFragmentWithActivity(parentTask);
final ActivityRecord activity2 = taskFragment2.getTopMostActivity();
activity2.setVisible(true);
- activity2.mVisibleRequested = false;
+ activity2.setVisibleRequested(false);
final ArraySet<ActivityRecord> opening = new ArraySet<>();
opening.add(activity1);
@@ -654,11 +654,11 @@ public class AppTransitionControllerTest extends WindowTestsBase {
final TaskFragment taskFragment1 = createTaskFragmentWithActivity(task1);
final ActivityRecord activity1 = taskFragment1.getTopMostActivity();
activity1.setVisible(false);
- activity1.mVisibleRequested = true;
+ activity1.setVisibleRequested(true);
final ActivityRecord activity2 = createActivityRecord(mDisplayContent);
activity2.setVisible(true);
- activity2.mVisibleRequested = false;
+ activity2.setVisibleRequested(false);
final ArraySet<ActivityRecord> opening = new ArraySet<>();
opening.add(activity1);
@@ -683,11 +683,11 @@ public class AppTransitionControllerTest extends WindowTestsBase {
final TaskFragment taskFragment1 = createTaskFragmentWithActivity(task1);
final ActivityRecord activity1 = taskFragment1.getTopMostActivity();
activity1.setVisible(true);
- activity1.mVisibleRequested = false;
+ activity1.setVisibleRequested(false);
final ActivityRecord activity2 = createActivityRecord(mDisplayContent);
activity2.setVisible(false);
- activity2.mVisibleRequested = true;
+ activity2.setVisibleRequested(true);
final ArraySet<ActivityRecord> opening = new ArraySet<>();
opening.add(activity2);
@@ -710,13 +710,13 @@ public class AppTransitionControllerTest extends WindowTestsBase {
// +- [Task2] (embedded) - [ActivityRecord2] (opening, invisible)
final ActivityRecord activity1 = createActivityRecord(mDisplayContent);
activity1.setVisible(false);
- activity1.mVisibleRequested = true;
+ activity1.setVisibleRequested(true);
final Task task2 = createTask(mDisplayContent);
task2.mRemoveWithTaskOrganizer = true;
final ActivityRecord activity2 = createActivityRecord(task2);
activity2.setVisible(false);
- activity2.mVisibleRequested = true;
+ activity2.setVisibleRequested(true);
final ArraySet<ActivityRecord> opening = new ArraySet<>();
opening.add(activity1);
@@ -744,7 +744,7 @@ public class AppTransitionControllerTest extends WindowTestsBase {
final ActivityRecord activity1 = createActivityRecord(task);
activity1.setVisible(false);
- activity1.mVisibleRequested = true;
+ activity1.setVisibleRequested(true);
final ActivityRecord activity2 = createActivityRecord(task);
final ArraySet<ActivityRecord> opening = new ArraySet<>();
@@ -1260,6 +1260,8 @@ public class AppTransitionControllerTest extends WindowTestsBase {
@Test
public void testTransitionGoodToGoForTaskFragments_detachedApp() {
final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+ final ITaskFragmentOrganizer iOrganizer = getITaskFragmentOrganizer(organizer);
+ mAtm.mTaskFragmentOrganizerController.registerOrganizer(iOrganizer);
final Task task = createTask(mDisplayContent);
final TaskFragment changeTaskFragment =
createTaskFragmentWithEmbeddedActivity(task, organizer);
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
index 437eeb1093ca..6b814e678419 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
@@ -385,11 +385,11 @@ public class AppTransitionTests extends WindowTestsBase {
// Simulate activity1 launches activity2.
final ActivityRecord activity1 = createActivityRecord(task);
activity1.setVisible(true);
- activity1.mVisibleRequested = false;
+ activity1.setVisibleRequested(false);
activity1.allDrawn = true;
final ActivityRecord activity2 = createActivityRecord(task);
activity2.setVisible(false);
- activity2.mVisibleRequested = true;
+ activity2.setVisibleRequested(true);
activity2.allDrawn = true;
dc.mClosingApps.add(activity1);
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
index dc3515dec2f5..1b77c95358ad 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
@@ -20,35 +20,33 @@ import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_EXT_ENABLE_ON_BACK
import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
import static android.window.BackNavigationInfo.typeToString;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.hardware.HardwareBuffer;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
import android.view.WindowManager;
import android.window.BackAnimationAdapter;
-import android.window.BackEvent;
+import android.window.BackMotionEvent;
import android.window.BackNavigationInfo;
import android.window.IOnBackInvokedCallback;
import android.window.OnBackInvokedCallback;
import android.window.OnBackInvokedCallbackInfo;
import android.window.OnBackInvokedDispatcher;
-import android.window.TaskSnapshot;
import android.window.WindowOnBackInvokedDispatcher;
import com.android.server.LocalServices;
@@ -67,6 +65,7 @@ public class BackNavigationControllerTests extends WindowTestsBase {
private BackNavigationController mBackNavigationController;
private WindowManagerInternal mWindowManagerInternal;
private BackAnimationAdapter mBackAnimationAdapter;
+ private Task mRootHomeTask;
@Before
public void setUp() throws Exception {
@@ -76,6 +75,7 @@ public class BackNavigationControllerTests extends WindowTestsBase {
LocalServices.addService(WindowManagerInternal.class, mWindowManagerInternal);
mBackNavigationController.setWindowManager(mWm);
mBackAnimationAdapter = mock(BackAnimationAdapter.class);
+ mRootHomeTask = initHomeActivity();
}
@Test
@@ -101,7 +101,8 @@ public class BackNavigationControllerTests extends WindowTestsBase {
ActivityRecord recordA = createActivityRecord(taskA);
Mockito.doNothing().when(recordA).reparentSurfaceControl(any(), any());
- withSystemCallback(createTopTaskWithActivity());
+ final Task topTask = createTopTaskWithActivity();
+ withSystemCallback(topTask);
BackNavigationInfo backNavigationInfo = startBackNavigation();
assertWithMessage("BackNavigationInfo").that(backNavigationInfo).isNotNull();
assertThat(typeToString(backNavigationInfo.getType()))
@@ -111,6 +112,20 @@ public class BackNavigationControllerTests extends WindowTestsBase {
verify(mBackNavigationController).scheduleAnimationLocked(
eq(BackNavigationInfo.TYPE_CROSS_TASK), any(), eq(mBackAnimationAdapter),
any());
+
+ // reset drawning status
+ topTask.forAllWindows(w -> {
+ makeWindowVisibleAndDrawn(w);
+ }, true);
+ setupKeyguardOccluded();
+ backNavigationInfo = startBackNavigation();
+ assertThat(typeToString(backNavigationInfo.getType()))
+ .isEqualTo(typeToString(BackNavigationInfo.TYPE_CALLBACK));
+
+ doReturn(true).when(recordA).canShowWhenLocked();
+ backNavigationInfo = startBackNavigation();
+ assertThat(typeToString(backNavigationInfo.getType()))
+ .isEqualTo(typeToString(BackNavigationInfo.TYPE_CROSS_TASK));
}
@Test
@@ -137,6 +152,20 @@ public class BackNavigationControllerTests extends WindowTestsBase {
assertThat(backNavigationInfo.getOnBackInvokedCallback()).isEqualTo(callback);
assertThat(typeToString(backNavigationInfo.getType()))
.isEqualTo(typeToString(BackNavigationInfo.TYPE_CROSS_ACTIVITY));
+
+ // reset drawing status
+ testCase.recordFront.forAllWindows(w -> {
+ makeWindowVisibleAndDrawn(w);
+ }, true);
+ setupKeyguardOccluded();
+ backNavigationInfo = startBackNavigation();
+ assertThat(typeToString(backNavigationInfo.getType()))
+ .isEqualTo(typeToString(BackNavigationInfo.TYPE_CALLBACK));
+
+ doReturn(true).when(testCase.recordBack).canShowWhenLocked();
+ backNavigationInfo = startBackNavigation();
+ assertThat(typeToString(backNavigationInfo.getType()))
+ .isEqualTo(typeToString(BackNavigationInfo.TYPE_CROSS_ACTIVITY));
}
@Test
@@ -150,6 +179,7 @@ public class BackNavigationControllerTests extends WindowTestsBase {
WindowState window = createWindow(null, WindowManager.LayoutParams.TYPE_WALLPAPER,
"Wallpaper");
addToWindowMap(window, true);
+ makeWindowVisibleAndDrawn(window);
IOnBackInvokedCallback callback = createOnBackInvokedCallback();
window.setOnBackInvokedCallbackInfo(
@@ -163,12 +193,17 @@ public class BackNavigationControllerTests extends WindowTestsBase {
@Test
public void preparesForBackToHome() {
- Task task = createTopTaskWithActivity();
- withSystemCallback(task);
+ final Task topTask = createTopTaskWithActivity();
+ withSystemCallback(topTask);
BackNavigationInfo backNavigationInfo = startBackNavigation();
assertThat(typeToString(backNavigationInfo.getType()))
.isEqualTo(typeToString(BackNavigationInfo.TYPE_RETURN_TO_HOME));
+
+ setupKeyguardOccluded();
+ backNavigationInfo = startBackNavigation();
+ assertThat(typeToString(backNavigationInfo.getType()))
+ .isEqualTo(typeToString(BackNavigationInfo.TYPE_CALLBACK));
}
@Test
@@ -236,6 +271,20 @@ public class BackNavigationControllerTests extends WindowTestsBase {
1, appLatch.getCount());
}
+ @Test
+ public void backInfoWindowWithoutDrawn() {
+ WindowState window = createWindow(null, WindowManager.LayoutParams.TYPE_APPLICATION,
+ "TestWindow");
+ addToWindowMap(window, true);
+
+ IOnBackInvokedCallback callback = createOnBackInvokedCallback();
+ window.setOnBackInvokedCallbackInfo(
+ new OnBackInvokedCallbackInfo(callback, OnBackInvokedDispatcher.PRIORITY_DEFAULT));
+
+ BackNavigationInfo backNavigationInfo = startBackNavigation();
+ assertThat(backNavigationInfo).isNull();
+ }
+
private IOnBackInvokedCallback withSystemCallback(Task task) {
IOnBackInvokedCallback callback = createOnBackInvokedCallback();
task.getTopMostActivity().getTopChild().setOnBackInvokedCallbackInfo(
@@ -259,11 +308,11 @@ public class BackNavigationControllerTests extends WindowTestsBase {
private IOnBackInvokedCallback createOnBackInvokedCallback() {
return new IOnBackInvokedCallback.Stub() {
@Override
- public void onBackStarted(BackEvent backEvent) {
+ public void onBackStarted(BackMotionEvent backMotionEvent) {
}
@Override
- public void onBackProgressed(BackEvent backEvent) {
+ public void onBackProgressed(BackMotionEvent backMotionEvent) {
}
@Override
@@ -287,14 +336,22 @@ public class BackNavigationControllerTests extends WindowTestsBase {
};
}
- @NonNull
- private TaskSnapshotController createMockTaskSnapshotController() {
- TaskSnapshotController taskSnapshotController = mock(TaskSnapshotController.class);
- TaskSnapshot taskSnapshot = mock(TaskSnapshot.class);
- when(taskSnapshot.getHardwareBuffer()).thenReturn(mock(HardwareBuffer.class));
- when(taskSnapshotController.getSnapshot(anyInt(), anyInt(), anyBoolean(), anyBoolean()))
- .thenReturn(taskSnapshot);
- return taskSnapshotController;
+ private Task initHomeActivity() {
+ final Task task = mDisplayContent.getDefaultTaskDisplayArea().getRootHomeTask();
+ task.forAllLeafTasks((t) -> {
+ if (t.getTopMostActivity() == null) {
+ final ActivityRecord r = createActivityRecord(t);
+ Mockito.doNothing().when(t).reparentSurfaceControl(any(), any());
+ Mockito.doNothing().when(r).reparentSurfaceControl(any(), any());
+ }
+ }, true);
+ return task;
+ }
+
+ private void setupKeyguardOccluded() {
+ final KeyguardController kc = mRootHomeTask.mTaskSupervisor.getKeyguardController();
+ doReturn(true).when(kc).isKeyguardLocked(anyInt());
+ doReturn(true).when(kc).isDisplayOccluded(anyInt());
}
@NonNull
@@ -309,6 +366,7 @@ public class BackNavigationControllerTests extends WindowTestsBase {
Mockito.doNothing().when(task).reparentSurfaceControl(any(), any());
mAtm.setFocusedTask(task.mTaskId, record);
addToWindowMap(window, true);
+ makeWindowVisibleAndDrawn(window);
return task;
}
@@ -333,6 +391,8 @@ public class BackNavigationControllerTests extends WindowTestsBase {
addToWindowMap(window1, true);
addToWindowMap(window2, true);
+ makeWindowVisibleAndDrawn(window2);
+
CrossActivityTestCase testCase = new CrossActivityTestCase();
testCase.task = task;
testCase.recordBack = record1;
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index d1511880451e..bc23fa399f44 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -590,7 +590,7 @@ public class DisplayContentTests extends WindowTestsBase {
assertEquals(window1, mWm.mRoot.getTopFocusedDisplayContent().mCurrentFocus);
// Make sure top focused display not changed if there is a focused app.
- window1.mActivityRecord.mVisibleRequested = false;
+ window1.mActivityRecord.setVisibleRequested(false);
window1.getDisplayContent().setFocusedApp(window1.mActivityRecord);
updateFocusedWindow();
assertTrue(!window1.isFocused());
@@ -1106,7 +1106,7 @@ public class DisplayContentTests extends WindowTestsBase {
public void testOrientationBehind() {
final ActivityRecord prev = new ActivityBuilder(mAtm).setCreateTask(true)
.setScreenOrientation(getRotatedOrientation(mDisplayContent)).build();
- prev.mVisibleRequested = false;
+ prev.setVisibleRequested(false);
final ActivityRecord top = new ActivityBuilder(mAtm).setCreateTask(true)
.setScreenOrientation(SCREEN_ORIENTATION_BEHIND).build();
assertNotEquals(WindowConfiguration.ROTATION_UNDEFINED,
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerTests.java
index db1d15a4584a..ba68a25a9fb4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerTests.java
@@ -243,7 +243,7 @@ public class DisplayWindowPolicyControllerTests extends WindowTestsBase {
}
@Override
- public boolean canShowTasksInRecents() {
+ public boolean canShowTasksInHostDeviceRecents() {
return true;
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
index ac2df62d6305..6f2e3f2d3221 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
@@ -819,7 +819,7 @@ public class RecentTasksTest extends WindowTestsBase {
@Test
public void testVisibleTask_displayCanNotShowTaskFromRecents_expectNotVisible() {
final DisplayContent displayContent = addNewDisplayContentAt(DisplayContent.POSITION_TOP);
- doReturn(false).when(displayContent).canShowTasksInRecents();
+ doReturn(false).when(displayContent).canShowTasksInHostDeviceRecents();
final Task task = displayContent.getDefaultTaskDisplayArea().createRootTask(
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */);
mRecentTasks.add(task);
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
index 88e58eab58aa..08635ab70d31 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
@@ -172,12 +172,12 @@ public class RecentsAnimationControllerTest extends WindowTestsBase {
// executed.
final ActivityRecord activity1 = createActivityRecord(task);
activity1.setVisible(true);
- activity1.mVisibleRequested = false;
+ activity1.setVisibleRequested(false);
activity1.addWindow(createWindowState(new LayoutParams(TYPE_BASE_APPLICATION), activity1));
final ActivityRecord activity2 = createActivityRecord(task);
activity2.setVisible(false);
- activity2.mVisibleRequested = true;
+ activity2.setVisibleRequested(true);
mDefaultDisplay.getConfiguration().windowConfiguration.setRotation(
mDefaultDisplay.getRotation());
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
index a2b4cb867d6b..de3a526573f8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
@@ -111,7 +111,7 @@ public class RecentsAnimationTest extends WindowTestsBase {
RecentsAnimationCallbacks recentsAnimation = startRecentsActivity(
mRecentsComponent, true /* getRecentsAnimation */);
// The launch-behind state should make the recents activity visible.
- assertTrue(recentActivity.mVisibleRequested);
+ assertTrue(recentActivity.isVisibleRequested());
assertEquals(ActivityTaskManagerService.DEMOTE_TOP_REASON_ANIMATING_RECENTS,
mAtm.mDemoteTopAppReasons);
assertFalse(mAtm.mInternal.useTopSchedGroupForTopProcess());
@@ -119,7 +119,7 @@ public class RecentsAnimationTest extends WindowTestsBase {
// Simulate the animation is cancelled without changing the stack order.
recentsAnimation.onAnimationFinished(REORDER_KEEP_IN_PLACE, false /* sendUserLeaveHint */);
// The non-top recents activity should be invisible by the restored launch-behind state.
- assertFalse(recentActivity.mVisibleRequested);
+ assertFalse(recentActivity.isVisibleRequested());
assertEquals(0, mAtm.mDemoteTopAppReasons);
}
@@ -164,7 +164,7 @@ public class RecentsAnimationTest extends WindowTestsBase {
// The activity is started in background so it should be invisible and will be stopped.
assertThat(recentsActivity).isNotNull();
assertThat(mSupervisor.mStoppingActivities).contains(recentsActivity);
- assertFalse(recentsActivity.mVisibleRequested);
+ assertFalse(recentsActivity.isVisibleRequested());
// Assume it is stopped to test next use case.
recentsActivity.activityStopped(null /* newIcicle */, null /* newPersistentState */,
@@ -360,7 +360,7 @@ public class RecentsAnimationTest extends WindowTestsBase {
true);
// Ensure we find the task for the right user and it is made visible
- assertTrue(otherUserHomeActivity.mVisibleRequested);
+ assertTrue(otherUserHomeActivity.isVisibleRequested());
}
private void startRecentsActivity() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
index 881cc5f94f44..fb29d3adb52b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
@@ -1068,7 +1068,7 @@ public class RootTaskTests extends WindowTestsBase {
activity.app = null;
overlayActivity.app = null;
// Simulate the process is dead
- activity.mVisibleRequested = false;
+ activity.setVisibleRequested(false);
activity.setState(DESTROYED, "Test");
assertEquals(2, task.getChildCount());
@@ -1205,7 +1205,7 @@ public class RootTaskTests extends WindowTestsBase {
// There is still an activity1 in rootTask1 so the activity2 should be added to finishing
// list that will be destroyed until idle.
- rootTask2.getTopNonFinishingActivity().mVisibleRequested = true;
+ rootTask2.getTopNonFinishingActivity().setVisibleRequested(true);
final ActivityRecord activity2 = finishTopActivity(rootTask2);
assertEquals(STOPPING, activity2.getState());
assertThat(mSupervisor.mStoppingActivities).contains(activity2);
@@ -1410,7 +1410,7 @@ public class RootTaskTests extends WindowTestsBase {
new ActivityBuilder(mAtm).setTask(task).build();
// The scenario we are testing is when the app isn't visible yet.
nonTopVisibleActivity.setVisible(false);
- nonTopVisibleActivity.mVisibleRequested = false;
+ nonTopVisibleActivity.setVisibleRequested(false);
doReturn(false).when(nonTopVisibleActivity).attachedToProcess();
doReturn(true).when(nonTopVisibleActivity).shouldBeVisibleUnchecked();
doNothing().when(mSupervisor).startSpecificActivity(any(), anyBoolean(),
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
index f84865bce0fa..a17e124e8216 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
@@ -174,7 +174,7 @@ public class RootWindowContainerTests extends WindowTestsBase {
final Task rootTask = new TaskBuilder(mSupervisor).build();
final Task task1 = new TaskBuilder(mSupervisor).setParentTask(rootTask).build();
final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task1).build();
- activity1.mVisibleRequested = true;
+ activity1.setVisibleRequested(true);
mWm.mRoot.rankTaskLayers();
assertEquals(1, task1.mLayerRank);
@@ -183,7 +183,7 @@ public class RootWindowContainerTests extends WindowTestsBase {
final Task task2 = new TaskBuilder(mSupervisor).build();
final ActivityRecord activity2 = new ActivityBuilder(mAtm).setTask(task2).build();
- activity2.mVisibleRequested = true;
+ activity2.setVisibleRequested(true);
mWm.mRoot.rankTaskLayers();
// Note that ensureActivitiesVisible is disabled in SystemServicesTestRule, so both the
@@ -200,8 +200,8 @@ public class RootWindowContainerTests extends WindowTestsBase {
assertEquals(2, task2.mLayerRank);
// The rank should be updated to invisible when device went to sleep.
- activity1.mVisibleRequested = false;
- activity2.mVisibleRequested = false;
+ activity1.setVisibleRequested(false);
+ activity2.setVisibleRequested(false);
doReturn(true).when(mAtm).isSleepingOrShuttingDownLocked();
doReturn(true).when(mRootWindowContainer).putTasksToSleep(anyBoolean(), anyBoolean());
mSupervisor.mGoingToSleepWakeLock = mock(PowerManager.WakeLock.class);
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 e65610f0959b..13ea99ae6fec 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -169,7 +169,7 @@ public class SizeCompatTests extends WindowTestsBase {
public void testRestartProcessIfVisible() {
setUpDisplaySizeWithApp(1000, 2500);
doNothing().when(mSupervisor).scheduleRestartTimeout(mActivity);
- mActivity.mVisibleRequested = true;
+ mActivity.setVisibleRequested(true);
mActivity.setSavedState(null /* savedState */);
mActivity.setState(RESUMED, "testRestart");
prepareUnresizable(mActivity, 1.5f /* maxAspect */, SCREEN_ORIENTATION_UNSPECIFIED);
@@ -553,7 +553,7 @@ public class SizeCompatTests extends WindowTestsBase {
resizeDisplay(display, 900, 1800);
mActivity.setState(STOPPED, "testSizeCompatMode");
- mActivity.mVisibleRequested = false;
+ mActivity.setVisibleRequested(false);
mActivity.visibleIgnoringKeyguard = false;
mActivity.app.setReportedProcState(ActivityManager.PROCESS_STATE_CACHED_ACTIVITY);
mActivity.app.computeProcessActivityState();
@@ -605,7 +605,7 @@ public class SizeCompatTests extends WindowTestsBase {
// Make the activity resizable again by restarting it
clearInvocations(mTask);
mActivity.info.resizeMode = RESIZE_MODE_RESIZEABLE;
- mActivity.mVisibleRequested = true;
+ mActivity.setVisibleRequested(true);
mActivity.restartProcessIfVisible();
// The full lifecycle isn't hooked up so manually set state to resumed
mActivity.setState(RESUMED, "testHandleActivitySizeCompatModeChanged");
@@ -3188,7 +3188,7 @@ public class SizeCompatTests extends WindowTestsBase {
task.mResizeMode = activity.info.resizeMode;
task.getRootActivity().info.resizeMode = activity.info.resizeMode;
}
- activity.mVisibleRequested = true;
+ activity.setVisibleRequested(true);
if (maxAspect >= 0) {
activity.info.setMaxAspectRatio(maxAspect);
}
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 d5fb1a856e31..91f8d8d186c6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
@@ -398,7 +398,7 @@ public class TaskDisplayAreaTests extends WindowTestsBase {
.setParentTask(rootHomeTask).setCreateTask(true).build();
}
homeActivity.setVisible(false);
- homeActivity.mVisibleRequested = true;
+ homeActivity.setVisibleRequested(true);
assertFalse(rootHomeTask.isVisible());
assertEquals(defaultTaskDisplayArea.getOrientation(), rootHomeTask.getOrientation());
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index 2b493145f854..db65f49465bc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -370,7 +370,8 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
mController.onActivityReparentedToTask(activity);
mController.dispatchPendingEvents();
- assertTaskFragmentParentInfoChangedTransaction(task);
+ // There will not be TaskFragmentParentInfoChanged because Task visible request is changed
+ // before the organized TaskFragment is added to the Task.
assertActivityReparentedToTaskTransaction(task.mTaskId, activity.intent, activity.token);
}
@@ -552,10 +553,9 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
@Test
public void testApplyTransaction_enforceHierarchyChange_createTaskFragment() {
final ActivityRecord ownerActivity = createActivityRecord(mDisplayContent);
- final IBinder fragmentToken = new Binder();
// Allow organizer to create TaskFragment and start/reparent activity to TaskFragment.
- createTaskFragmentFromOrganizer(mTransaction, ownerActivity, fragmentToken);
+ createTaskFragmentFromOrganizer(mTransaction, ownerActivity, mFragmentToken);
mTransaction.startActivityInTaskFragment(
mFragmentToken, null /* callerToken */, new Intent(), null /* activityOptions */);
mTransaction.reparentActivityToTaskFragment(mFragmentToken, mock(IBinder.class));
@@ -564,7 +564,8 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
assertApplyTransactionAllowed(mTransaction);
// Successfully created a TaskFragment.
- final TaskFragment taskFragment = mWindowOrganizerController.getTaskFragment(fragmentToken);
+ final TaskFragment taskFragment = mWindowOrganizerController.getTaskFragment(
+ mFragmentToken);
assertNotNull(taskFragment);
assertEquals(ownerActivity.getTask(), taskFragment.getTask());
}
@@ -703,6 +704,40 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
}
@Test
+ public void testApplyTransaction_createTaskFragment_withPairedPrimaryFragmentToken() {
+ final Task task = createTask(mDisplayContent);
+ mTaskFragment = new TaskFragmentBuilder(mAtm)
+ .setParentTask(task)
+ .setFragmentToken(mFragmentToken)
+ .createActivityCount(1)
+ .build();
+ mWindowOrganizerController.mLaunchTaskFragments.put(mFragmentToken, mTaskFragment);
+ final ActivityRecord activityOnTop = createActivityRecord(task);
+ final int uid = Binder.getCallingUid();
+ activityOnTop.info.applicationInfo.uid = uid;
+ activityOnTop.getTask().effectiveUid = uid;
+ final IBinder fragmentToken1 = new Binder();
+ final TaskFragmentCreationParams params = new TaskFragmentCreationParams.Builder(
+ mOrganizerToken, fragmentToken1, activityOnTop.token)
+ .setPairedPrimaryFragmentToken(mFragmentToken)
+ .build();
+ mTransaction.setTaskFragmentOrganizer(mIOrganizer);
+ mTransaction.createTaskFragment(params);
+ assertApplyTransactionAllowed(mTransaction);
+
+ // Successfully created a TaskFragment.
+ final TaskFragment taskFragment = mWindowOrganizerController.getTaskFragment(
+ fragmentToken1);
+ assertNotNull(taskFragment);
+ // The new TaskFragment should be positioned right above the paired TaskFragment.
+ assertEquals(task.mChildren.indexOf(mTaskFragment) + 1,
+ task.mChildren.indexOf(taskFragment));
+ // The top activity should remain on top.
+ assertEquals(task.mChildren.indexOf(taskFragment) + 1,
+ task.mChildren.indexOf(activityOnTop));
+ }
+
+ @Test
public void testApplyTransaction_enforceHierarchyChange_reparentChildren() {
doReturn(true).when(mTaskFragment).isAttached();
@@ -1159,6 +1194,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
doReturn(false).when(task).shouldBeVisible(any());
// Dispatch the initial event in the Task to update the Task visibility to the organizer.
+ clearInvocations(mOrganizer);
mController.onTaskFragmentAppeared(mIOrganizer, taskFragment);
mController.dispatchPendingEvents();
verify(mOrganizer).onTransactionReady(any());
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
index 8fda1917d0b5..3ced84f6fcf7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -107,18 +107,49 @@ public class TaskFragmentTest extends WindowTestsBase {
}
@Test
+ public void testShouldStartChangeTransition_relativePositionChange() {
+ mockSurfaceFreezerSnapshot(mTaskFragment.mSurfaceFreezer);
+ final Rect startBounds = new Rect(0, 0, 500, 1000);
+ final Rect endBounds = new Rect(500, 0, 1000, 1000);
+ mTaskFragment.setBounds(startBounds);
+ mTaskFragment.updateRelativeEmbeddedBounds();
+ doReturn(true).when(mTaskFragment).isVisible();
+ doReturn(true).when(mTaskFragment).isVisibleRequested();
+
+ // Do not resize, just change the relative position.
+ final Rect relStartBounds = new Rect(mTaskFragment.getRelativeEmbeddedBounds());
+ mTaskFragment.setBounds(endBounds);
+ mTaskFragment.updateRelativeEmbeddedBounds();
+ spyOn(mDisplayContent.mTransitionController);
+
+ // For Shell transition, we don't want to take snapshot when the bounds are not resized
+ doReturn(true).when(mDisplayContent.mTransitionController)
+ .isShellTransitionsEnabled();
+ assertFalse(mTaskFragment.shouldStartChangeTransition(startBounds, relStartBounds));
+
+ // For legacy transition, we want to request a change transition even if it is just relative
+ // bounds change.
+ doReturn(false).when(mDisplayContent.mTransitionController)
+ .isShellTransitionsEnabled();
+ assertTrue(mTaskFragment.shouldStartChangeTransition(startBounds, relStartBounds));
+ }
+
+ @Test
public void testStartChangeTransition_resetSurface() {
mockSurfaceFreezerSnapshot(mTaskFragment.mSurfaceFreezer);
final Rect startBounds = new Rect(0, 0, 1000, 1000);
final Rect endBounds = new Rect(500, 500, 1000, 1000);
mTaskFragment.setBounds(startBounds);
+ mTaskFragment.updateRelativeEmbeddedBounds();
doReturn(true).when(mTaskFragment).isVisible();
doReturn(true).when(mTaskFragment).isVisibleRequested();
clearInvocations(mTransaction);
+ final Rect relStartBounds = new Rect(mTaskFragment.getRelativeEmbeddedBounds());
mTaskFragment.deferOrganizedTaskFragmentSurfaceUpdate();
mTaskFragment.setBounds(endBounds);
- assertTrue(mTaskFragment.shouldStartChangeTransition(startBounds));
+ mTaskFragment.updateRelativeEmbeddedBounds();
+ assertTrue(mTaskFragment.shouldStartChangeTransition(startBounds, relStartBounds));
mTaskFragment.initializeChangeTransition(startBounds);
mTaskFragment.continueOrganizedTaskFragmentSurfaceUpdate();
@@ -157,17 +188,20 @@ public class TaskFragmentTest extends WindowTestsBase {
final Rect startBounds = new Rect(0, 0, 1000, 1000);
final Rect endBounds = new Rect(500, 500, 1000, 1000);
mTaskFragment.setBounds(startBounds);
+ mTaskFragment.updateRelativeEmbeddedBounds();
doReturn(true).when(mTaskFragment).isVisible();
doReturn(true).when(mTaskFragment).isVisibleRequested();
+ final Rect relStartBounds = new Rect(mTaskFragment.getRelativeEmbeddedBounds());
final DisplayPolicy displayPolicy = mDisplayContent.getDisplayPolicy();
displayPolicy.screenTurnedOff();
assertFalse(mTaskFragment.okToAnimate());
mTaskFragment.setBounds(endBounds);
+ mTaskFragment.updateRelativeEmbeddedBounds();
- assertFalse(mTaskFragment.shouldStartChangeTransition(startBounds));
+ assertFalse(mTaskFragment.shouldStartChangeTransition(startBounds, relStartBounds));
}
/**
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 59a31b105717..aaf07b9c1ede 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -135,8 +135,8 @@ public class TransitionTests extends WindowTestsBase {
changes.put(closing, new Transition.ChangeInfo(true /* vis */, true /* exChg */));
fillChangeMap(changes, newTask);
// End states.
- closing.mVisibleRequested = false;
- opening.mVisibleRequested = true;
+ closing.setVisibleRequested(false);
+ opening.setVisibleRequested(true);
final int transit = transition.mType;
int flags = 0;
@@ -199,9 +199,9 @@ public class TransitionTests extends WindowTestsBase {
changes.put(closing, new Transition.ChangeInfo(true /* vis */, true /* exChg */));
fillChangeMap(changes, newTask);
// End states.
- closing.mVisibleRequested = false;
- opening.mVisibleRequested = true;
- opening2.mVisibleRequested = true;
+ closing.setVisibleRequested(false);
+ opening.setVisibleRequested(true);
+ opening2.setVisibleRequested(true);
final int transit = transition.mType;
int flags = 0;
@@ -250,8 +250,8 @@ public class TransitionTests extends WindowTestsBase {
fillChangeMap(changes, tda);
// End states.
- showing.mVisibleRequested = true;
- showing2.mVisibleRequested = true;
+ showing.setVisibleRequested(true);
+ showing2.setVisibleRequested(true);
final int transit = transition.mType;
int flags = 0;
@@ -286,16 +286,16 @@ public class TransitionTests extends WindowTestsBase {
final Task openTask = createTask(mDisplayContent);
final ActivityRecord opening = createActivityRecord(openTask);
- opening.mVisibleRequested = false; // starts invisible
+ opening.setVisibleRequested(false); // starts invisible
final Task closeTask = createTask(mDisplayContent);
final ActivityRecord closing = createActivityRecord(closeTask);
- closing.mVisibleRequested = true; // starts visible
+ closing.setVisibleRequested(true); // starts visible
transition.collectExistenceChange(openTask);
transition.collect(opening);
transition.collect(closing);
- opening.mVisibleRequested = true;
- closing.mVisibleRequested = false;
+ opening.setVisibleRequested(true);
+ closing.setVisibleRequested(false);
ArrayList<WindowContainer> targets = Transition.calculateTargets(
transition.mParticipants, transition.mChanges);
@@ -323,7 +323,7 @@ public class TransitionTests extends WindowTestsBase {
WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD);
final ActivityRecord act = createActivityRecord(tasks[i]);
// alternate so that the transition doesn't get promoted to the display area
- act.mVisibleRequested = (i % 2) == 0; // starts invisible
+ act.setVisibleRequested((i % 2) == 0); // starts invisible
}
// doesn't matter which order collected since participants is a set
@@ -331,7 +331,7 @@ public class TransitionTests extends WindowTestsBase {
transition.collectExistenceChange(tasks[i]);
final ActivityRecord act = tasks[i].getTopMostActivity();
transition.collect(act);
- tasks[i].getTopMostActivity().mVisibleRequested = (i % 2) != 0;
+ tasks[i].getTopMostActivity().setVisibleRequested((i % 2) != 0);
}
ArrayList<WindowContainer> targets = Transition.calculateTargets(
@@ -360,7 +360,7 @@ public class TransitionTests extends WindowTestsBase {
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
final ActivityRecord act = createActivityRecord(tasks[i]);
// alternate so that the transition doesn't get promoted to the display area
- act.mVisibleRequested = (i % 2) == 0; // starts invisible
+ act.setVisibleRequested((i % 2) == 0); // starts invisible
act.visibleIgnoringKeyguard = (i % 2) == 0;
if (i == showWallpaperTask) {
doReturn(true).when(act).showWallpaper();
@@ -381,7 +381,7 @@ public class TransitionTests extends WindowTestsBase {
transition.collectExistenceChange(tasks[i]);
final ActivityRecord act = tasks[i].getTopMostActivity();
transition.collect(act);
- tasks[i].getTopMostActivity().mVisibleRequested = (i % 2) != 0;
+ tasks[i].getTopMostActivity().setVisibleRequested((i % 2) != 0);
}
ArrayList<WindowContainer> targets = Transition.calculateTargets(
@@ -417,9 +417,9 @@ public class TransitionTests extends WindowTestsBase {
changes.put(closing, new Transition.ChangeInfo(true /* vis */, true /* exChg */));
fillChangeMap(changes, topTask);
// End states.
- showing.mVisibleRequested = true;
- closing.mVisibleRequested = false;
- hiding.mVisibleRequested = false;
+ showing.setVisibleRequested(true);
+ closing.setVisibleRequested(false);
+ hiding.setVisibleRequested(false);
participants.add(belowTask);
participants.add(hiding);
@@ -449,9 +449,9 @@ public class TransitionTests extends WindowTestsBase {
changes.put(closing, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
fillChangeMap(changes, topTask);
// End states.
- showing.mVisibleRequested = true;
- opening.mVisibleRequested = true;
- closing.mVisibleRequested = false;
+ showing.setVisibleRequested(true);
+ opening.setVisibleRequested(true);
+ closing.setVisibleRequested(false);
participants.add(belowTask);
participants.add(showing);
@@ -531,19 +531,19 @@ public class TransitionTests extends WindowTestsBase {
@Test
public void testOpenActivityInTheSameTaskWithDisplayChange() {
final ActivityRecord closing = createActivityRecord(mDisplayContent);
- closing.mVisibleRequested = true;
+ closing.setVisibleRequested(true);
final Task task = closing.getTask();
makeTaskOrganized(task);
final ActivityRecord opening = createActivityRecord(task);
- opening.mVisibleRequested = false;
+ opening.setVisibleRequested(false);
makeDisplayAreaOrganized(mDisplayContent.getDefaultTaskDisplayArea(), mDisplayContent);
final WindowContainer<?>[] wcs = { closing, opening, task, mDisplayContent };
final Transition transition = createTestTransition(TRANSIT_OPEN);
for (WindowContainer<?> wc : wcs) {
transition.collect(wc);
}
- closing.mVisibleRequested = false;
- opening.mVisibleRequested = true;
+ closing.setVisibleRequested(false);
+ opening.setVisibleRequested(true);
final int newRotation = mDisplayContent.getWindowConfiguration().getRotation() + 1;
for (WindowContainer<?> wc : wcs) {
wc.getWindowConfiguration().setRotation(newRotation);
@@ -586,9 +586,9 @@ public class TransitionTests extends WindowTestsBase {
changes.put(changeInChange, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
fillChangeMap(changes, openTask);
// End states.
- changeInChange.mVisibleRequested = true;
- openInOpen.mVisibleRequested = true;
- openInChange.mVisibleRequested = true;
+ changeInChange.setVisibleRequested(true);
+ openInOpen.setVisibleRequested(true);
+ openInChange.setVisibleRequested(true);
final int transit = transition.mType;
int flags = 0;
@@ -644,8 +644,8 @@ public class TransitionTests extends WindowTestsBase {
changes.put(closing, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
fillChangeMap(changes, newTask);
// End states.
- closing.mVisibleRequested = true;
- opening.mVisibleRequested = true;
+ closing.setVisibleRequested(true);
+ opening.setVisibleRequested(true);
final int transit = transition.mType;
int flags = 0;
@@ -685,8 +685,8 @@ public class TransitionTests extends WindowTestsBase {
changes.put(closing, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
fillChangeMap(changes, newTask);
// End states.
- closing.mVisibleRequested = true;
- opening.mVisibleRequested = true;
+ closing.setVisibleRequested(true);
+ opening.setVisibleRequested(true);
final int transit = transition.mType;
int flags = 0;
@@ -962,7 +962,7 @@ public class TransitionTests extends WindowTestsBase {
home.mTransitionController.requestStartTransition(transition, home.getTask(),
null /* remoteTransition */, null /* displayChange */);
transition.collectExistenceChange(home);
- home.mVisibleRequested = true;
+ home.setVisibleRequested(true);
mDisplayContent.setFixedRotationLaunchingAppUnchecked(home);
doReturn(true).when(home).hasFixedRotationTransform(any());
player.startTransition();
@@ -998,12 +998,12 @@ public class TransitionTests extends WindowTestsBase {
// Start out with task2 visible and set up a transition that closes task2 and opens task1
final Task task1 = createTask(mDisplayContent);
final ActivityRecord activity1 = createActivityRecord(task1);
- activity1.mVisibleRequested = false;
+ activity1.setVisibleRequested(false);
activity1.setVisible(false);
final Task task2 = createTask(mDisplayContent);
makeTaskOrganized(task1, task2);
final ActivityRecord activity2 = createActivityRecord(task1);
- activity2.mVisibleRequested = true;
+ activity2.setVisibleRequested(true);
activity2.setVisible(true);
openTransition.collectExistenceChange(task1);
@@ -1011,9 +1011,9 @@ public class TransitionTests extends WindowTestsBase {
openTransition.collectExistenceChange(task2);
openTransition.collectExistenceChange(activity2);
- activity1.mVisibleRequested = true;
+ activity1.setVisibleRequested(true);
activity1.setVisible(true);
- activity2.mVisibleRequested = false;
+ activity2.setVisibleRequested(false);
// Using abort to force-finish the sync (since we can't wait for drawing in unit test).
// We didn't call abort on the transition itself, so it will still run onTransactionReady
@@ -1029,8 +1029,8 @@ public class TransitionTests extends WindowTestsBase {
closeTransition.collectExistenceChange(task2);
closeTransition.collectExistenceChange(activity2);
- activity1.mVisibleRequested = false;
- activity2.mVisibleRequested = true;
+ activity1.setVisibleRequested(false);
+ activity2.setVisibleRequested(true);
openTransition.finishTransition();
@@ -1072,12 +1072,12 @@ public class TransitionTests extends WindowTestsBase {
// Start out with task2 visible and set up a transition that closes task2 and opens task1
final Task task1 = createTask(mDisplayContent);
final ActivityRecord activity1 = createActivityRecord(task1);
- activity1.mVisibleRequested = false;
+ activity1.setVisibleRequested(false);
activity1.setVisible(false);
final Task task2 = createTask(mDisplayContent);
makeTaskOrganized(task1, task2);
final ActivityRecord activity2 = createActivityRecord(task2);
- activity2.mVisibleRequested = true;
+ activity2.setVisibleRequested(true);
activity2.setVisible(true);
openTransition.collectExistenceChange(task1);
@@ -1085,9 +1085,9 @@ public class TransitionTests extends WindowTestsBase {
openTransition.collectExistenceChange(task2);
openTransition.collectExistenceChange(activity2);
- activity1.mVisibleRequested = true;
+ activity1.setVisibleRequested(true);
activity1.setVisible(true);
- activity2.mVisibleRequested = false;
+ activity2.setVisibleRequested(false);
// Using abort to force-finish the sync (since we can't wait for drawing in unit test).
// We didn't call abort on the transition itself, so it will still run onTransactionReady
@@ -1107,8 +1107,8 @@ public class TransitionTests extends WindowTestsBase {
closeTransition.collectExistenceChange(activity2);
closeTransition.setTransientLaunch(activity2, null /* restoreBelow */);
- activity1.mVisibleRequested = false;
- activity2.mVisibleRequested = true;
+ activity1.setVisibleRequested(false);
+ activity2.setVisibleRequested(true);
activity2.setVisible(true);
// Using abort to force-finish the sync (since we obviously can't wait for drawing).
@@ -1166,8 +1166,8 @@ public class TransitionTests extends WindowTestsBase {
changes.put(activity0, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
changes.put(activity1, new Transition.ChangeInfo(false /* vis */, false /* exChg */));
// End states.
- activity0.mVisibleRequested = false;
- activity1.mVisibleRequested = true;
+ activity0.setVisibleRequested(false);
+ activity1.setVisibleRequested(true);
participants.add(activity0);
participants.add(activity1);
@@ -1210,9 +1210,9 @@ public class TransitionTests extends WindowTestsBase {
changes.put(nonEmbeddedActivity, new Transition.ChangeInfo(true /* vis */,
false /* exChg */));
// End states.
- closingActivity.mVisibleRequested = false;
- openingActivity.mVisibleRequested = true;
- nonEmbeddedActivity.mVisibleRequested = false;
+ closingActivity.setVisibleRequested(false);
+ openingActivity.setVisibleRequested(true);
+ nonEmbeddedActivity.setVisibleRequested(false);
participants.add(closingActivity);
participants.add(openingActivity);
@@ -1255,8 +1255,8 @@ public class TransitionTests extends WindowTestsBase {
false /* exChg */));
changes.put(embeddedTf, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
// End states.
- nonEmbeddedActivity.mVisibleRequested = false;
- embeddedActivity.mVisibleRequested = true;
+ nonEmbeddedActivity.setVisibleRequested(false);
+ embeddedActivity.setVisibleRequested(true);
embeddedTf.setBounds(new Rect(0, 0, 500, 500));
participants.add(nonEmbeddedActivity);
@@ -1285,11 +1285,11 @@ public class TransitionTests extends WindowTestsBase {
final ActivityRecord activity = createActivityRecord(task);
// Start states: set bounds to make sure the start bounds is ignored if it is not visible.
activity.getConfiguration().windowConfiguration.setBounds(new Rect(0, 0, 250, 500));
- activity.mVisibleRequested = false;
+ activity.setVisibleRequested(false);
changes.put(activity, new Transition.ChangeInfo(activity));
// End states: reset bounds to fill Task.
activity.getConfiguration().windowConfiguration.setBounds(taskBounds);
- activity.mVisibleRequested = true;
+ activity.setVisibleRequested(true);
participants.add(activity);
final ArrayList<WindowContainer> targets = Transition.calculateTargets(
@@ -1313,11 +1313,11 @@ public class TransitionTests extends WindowTestsBase {
task.getConfiguration().windowConfiguration.setBounds(taskBounds);
final ActivityRecord activity = createActivityRecord(task);
// Start states: fills Task without override.
- activity.mVisibleRequested = true;
+ activity.setVisibleRequested(true);
changes.put(activity, new Transition.ChangeInfo(activity));
// End states: set bounds to make sure the start bounds is ignored if it is not visible.
activity.getConfiguration().windowConfiguration.setBounds(new Rect(0, 0, 250, 500));
- activity.mVisibleRequested = false;
+ activity.setVisibleRequested(false);
participants.add(activity);
final ArrayList<WindowContainer> targets = Transition.calculateTargets(
@@ -1340,12 +1340,12 @@ public class TransitionTests extends WindowTestsBase {
final Task lastParent = createTask(mDisplayContent);
final Task newParent = createTask(mDisplayContent);
final ActivityRecord activity = createActivityRecord(lastParent);
- activity.mVisibleRequested = true;
+ activity.setVisibleRequested(true);
// Skip manipulate the SurfaceControl.
doNothing().when(activity).setDropInputMode(anyInt());
changes.put(activity, new Transition.ChangeInfo(activity));
activity.reparent(newParent, POSITION_TOP);
- activity.mVisibleRequested = false;
+ activity.setVisibleRequested(false);
participants.add(activity);
final ArrayList<WindowContainer> targets = Transition.calculateTargets(
@@ -1365,7 +1365,7 @@ public class TransitionTests extends WindowTestsBase {
final Task task = createTask(mDisplayContent);
task.setBounds(new Rect(0, 0, 2000, 1000));
final ActivityRecord activity = createActivityRecord(task);
- activity.mVisibleRequested = true;
+ activity.setVisibleRequested(true);
// Skip manipulate the SurfaceControl.
doNothing().when(activity).setDropInputMode(anyInt());
final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
@@ -1413,13 +1413,13 @@ public class TransitionTests extends WindowTestsBase {
task.setTaskDescription(taskDescription);
// Start states:
- embeddedActivity.mVisibleRequested = true;
- nonEmbeddedActivity.mVisibleRequested = false;
+ embeddedActivity.setVisibleRequested(true);
+ nonEmbeddedActivity.setVisibleRequested(false);
changes.put(embeddedTf, new Transition.ChangeInfo(embeddedTf));
changes.put(nonEmbeddedActivity, new Transition.ChangeInfo(nonEmbeddedActivity));
// End states:
- embeddedActivity.mVisibleRequested = false;
- nonEmbeddedActivity.mVisibleRequested = true;
+ embeddedActivity.setVisibleRequested(false);
+ nonEmbeddedActivity.setVisibleRequested(true);
participants.add(embeddedTf);
participants.add(nonEmbeddedActivity);
@@ -1532,7 +1532,7 @@ public class TransitionTests extends WindowTestsBase {
final ActivityRecord activity = createActivityRecord(lastParent);
doReturn(true).when(lastParent).shouldRemoveSelfOnLastChildRemoval();
doNothing().when(activity).setDropInputMode(anyInt());
- activity.mVisibleRequested = true;
+ activity.setVisibleRequested(true);
final Transition transition = new Transition(TRANSIT_CHANGE, 0 /* flags */,
activity.mTransitionController, mWm.mSyncEngine);
diff --git a/services/tests/wmtests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java
index 45e114130420..2fccb88ad8de 100644
--- a/services/tests/wmtests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java
@@ -95,7 +95,7 @@ public class UnknownAppVisibilityControllerTest extends WindowTestsBase {
final ActivityRecord activity = createNonAttachedActivityRecord(mDisplayContent);
mDisplayContent.mUnknownAppVisibilityController.notifyLaunched(activity);
activity.finishing = true;
- activity.mVisibleRequested = true;
+ activity.setVisibleRequested(true);
activity.setVisibility(false, false);
assertTrue(mDisplayContent.mUnknownAppVisibilityController.allResolved());
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
index 94b5b93e74c2..a100b9aced21 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
@@ -313,12 +313,12 @@ public class WallpaperControllerTests extends WindowTestsBase {
r.applyFixedRotationTransform(mDisplayContent.getDisplayInfo(),
mDisplayContent.mDisplayFrames, mDisplayContent.getConfiguration());
// Invisible requested activity should not share its rotation transform.
- r.mVisibleRequested = false;
+ r.setVisibleRequested(false);
mDisplayContent.mWallpaperController.adjustWallpaperWindows();
assertFalse(wallpaperToken.hasFixedRotationTransform());
// Wallpaper should link the transform of its target.
- r.mVisibleRequested = true;
+ r.setVisibleRequested(true);
mDisplayContent.mWallpaperController.adjustWallpaperWindows();
assertEquals(appWin, mDisplayContent.mWallpaperController.getWallpaperTarget());
assertTrue(r.hasFixedRotationTransform());
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 871030f43716..3d777f81579b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -205,7 +205,7 @@ public class WindowManagerServiceTests extends WindowTestsBase {
win.mViewVisibility = View.VISIBLE;
win.mHasSurface = true;
win.mActivityRecord.mAppStopped = true;
- win.mActivityRecord.mVisibleRequested = false;
+ win.mActivityRecord.setVisibleRequested(false);
win.mActivityRecord.setVisible(false);
mWm.mWindowMap.put(win.mClient.asBinder(), win);
final int w = 100;
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index c73e2378c849..e5e9f54626e3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -992,7 +992,7 @@ public class WindowOrganizerTests extends WindowTestsBase {
final Task task = createTask(rootTaskController1);
final WindowState w = createAppWindow(task, TYPE_APPLICATION, "Enlightened Window");
- w.mActivityRecord.mVisibleRequested = true;
+ w.mActivityRecord.setVisibleRequested(true);
w.mActivityRecord.setVisible(true);
BLASTSyncEngine bse = new BLASTSyncEngine(mWm);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
index 3abf7ce665ae..8bd414856394 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
@@ -324,7 +324,7 @@ public class WindowProcessControllerTests extends WindowTestsBase {
@Test
public void testComputeOomAdjFromActivities() {
final ActivityRecord activity = createActivityRecord(mWpc);
- activity.mVisibleRequested = true;
+ activity.setVisibleRequested(true);
final int[] callbackResult = { 0 };
final int visible = 1;
final int paused = 2;
@@ -359,7 +359,7 @@ public class WindowProcessControllerTests extends WindowTestsBase {
assertEquals(visible, callbackResult[0]);
callbackResult[0] = 0;
- activity.mVisibleRequested = false;
+ activity.setVisibleRequested(false);
activity.setState(PAUSED, "test");
mWpc.computeOomAdjFromActivities(callback);
assertEquals(paused, callbackResult[0]);
@@ -380,7 +380,7 @@ public class WindowProcessControllerTests extends WindowTestsBase {
final VisibleActivityProcessTracker tracker = mAtm.mVisibleActivityProcessTracker;
spyOn(tracker);
final ActivityRecord activity = createActivityRecord(mWpc);
- activity.mVisibleRequested = true;
+ activity.setVisibleRequested(true);
activity.setState(STARTED, "test");
verify(tracker).onAnyActivityVisible(mWpc);
@@ -398,7 +398,7 @@ public class WindowProcessControllerTests extends WindowTestsBase {
assertTrue(mWpc.hasForegroundActivities());
activity.setVisibility(false);
- activity.mVisibleRequested = false;
+ activity.setVisibleRequested(false);
activity.setState(STOPPED, "test");
verify(tracker).onAllActivitiesInvisible(mWpc);
@@ -413,7 +413,7 @@ public class WindowProcessControllerTests extends WindowTestsBase {
@Test
public void testTopActivityUiModeChangeScheduleConfigChange() {
final ActivityRecord activity = createActivityRecord(mWpc);
- activity.mVisibleRequested = true;
+ activity.setVisibleRequested(true);
doReturn(true).when(activity).applyAppSpecificConfig(anyInt(), any());
mWpc.updateAppSpecificSettingsForAllActivitiesInPackage(DEFAULT_COMPONENT_PACKAGE_NAME,
Configuration.UI_MODE_NIGHT_YES, LocaleList.forLanguageTags("en-XA"));
@@ -423,7 +423,7 @@ public class WindowProcessControllerTests extends WindowTestsBase {
@Test
public void testTopActivityUiModeChangeForDifferentPackage_noScheduledConfigChange() {
final ActivityRecord activity = createActivityRecord(mWpc);
- activity.mVisibleRequested = true;
+ activity.setVisibleRequested(true);
mWpc.updateAppSpecificSettingsForAllActivitiesInPackage("com.different.package",
Configuration.UI_MODE_NIGHT_YES, LocaleList.forLanguageTags("en-XA"));
verify(activity, never()).applyAppSpecificConfig(anyInt(), any());
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index 1b79dd35082e..69e3244af1b6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -264,7 +264,7 @@ public class WindowStateTests extends WindowTestsBase {
// Verify that app window can still be IME target as long as it is visible (even if
// it is going to become invisible).
- appWindow.mActivityRecord.mVisibleRequested = false;
+ appWindow.mActivityRecord.setVisibleRequested(false);
assertTrue(appWindow.canBeImeTarget());
// Make windows invisible
@@ -413,6 +413,16 @@ public class WindowStateTests extends WindowTestsBase {
}
@Test
+ public void testCanAffectSystemUiFlags_starting() {
+ final WindowState app = createWindow(null, TYPE_APPLICATION_STARTING, "app");
+ app.mActivityRecord.setVisible(true);
+ app.mStartingData = new SnapshotStartingData(mWm, null, 0);
+ assertFalse(app.canAffectSystemUiFlags());
+ app.mStartingData = new SplashScreenStartingData(mWm, 0, 0);
+ assertTrue(app.canAffectSystemUiFlags());
+ }
+
+ @Test
public void testCanAffectSystemUiFlags_disallow() {
final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
app.mActivityRecord.setVisible(true);
@@ -720,7 +730,7 @@ public class WindowStateTests extends WindowTestsBase {
// No need to wait for a window of invisible activity even if the window has surface.
final WindowState invisibleApp = mAppWindow;
- invisibleApp.mActivityRecord.mVisibleRequested = false;
+ invisibleApp.mActivityRecord.setVisibleRequested(false);
invisibleApp.mActivityRecord.allDrawn = false;
outWaitingForDrawn.clear();
invisibleApp.requestDrawIfNeeded(outWaitingForDrawn);
@@ -738,7 +748,7 @@ public class WindowStateTests extends WindowTestsBase {
assertFalse(startingApp.getOrientationChanging());
// Even if the display is frozen, invisible requested window should not be affected.
- startingApp.mActivityRecord.mVisibleRequested = false;
+ startingApp.mActivityRecord.setVisibleRequested(false);
mWm.startFreezingDisplay(0, 0, mDisplayContent);
doReturn(true).when(mWm.mPolicy).isScreenOn();
startingApp.getWindowFrames().setInsetsChanged(true);
@@ -813,7 +823,7 @@ public class WindowStateTests extends WindowTestsBase {
final WindowState win = createWindow(null /* parent */, TYPE_APPLICATION, embeddedActivity,
"App window");
doReturn(true).when(embeddedActivity).isVisible();
- embeddedActivity.mVisibleRequested = true;
+ embeddedActivity.setVisibleRequested(true);
makeWindowVisible(win);
win.mLayoutSeq = win.getDisplayContent().mLayoutSeq;
// Set the bounds twice:
@@ -838,7 +848,7 @@ public class WindowStateTests extends WindowTestsBase {
@Test
public void testCantReceiveTouchWhenAppTokenHiddenRequested() {
final WindowState win0 = createWindow(null, TYPE_APPLICATION, "win0");
- win0.mActivityRecord.mVisibleRequested = false;
+ win0.mActivityRecord.setVisibleRequested(false);
assertFalse(win0.canReceiveTouchInput());
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 5a261bc6526b..019b14db161c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -738,7 +738,7 @@ class WindowTestsBase extends SystemServiceTestsBase {
activity.onDisplayChanged(dc);
activity.setOccludesParent(true);
activity.setVisible(true);
- activity.mVisibleRequested = true;
+ activity.setVisibleRequested(true);
}
/**
@@ -1240,7 +1240,7 @@ class WindowTestsBase extends SystemServiceTestsBase {
mTask.moveToFront("createActivity");
}
if (mVisible) {
- activity.mVisibleRequested = true;
+ activity.setVisibleRequested(true);
activity.setVisible(true);
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/utils/StateMachineTest.java b/services/tests/wmtests/src/com/android/server/wm/utils/StateMachineTest.java
new file mode 100644
index 000000000000..e82a7c222b8f
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/utils/StateMachineTest.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.utils;
+
+import static com.android.server.wm.utils.StateMachine.isIn;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+
+/**
+ * Build/Install/Run:
+ * atest WmTests:StateMachineTest
+ */
+@SmallTest
+@Presubmit
+public class StateMachineTest {
+ static class LoggingHandler implements StateMachine.Handler {
+ final int mState;
+ final StringBuffer mStringBuffer;
+ // True if process #handle
+ final boolean mHandleSelf;
+
+ LoggingHandler(int state, StringBuffer sb, boolean handleSelf) {
+ mHandleSelf = handleSelf;
+ mState = state;
+ mStringBuffer = sb;
+ }
+
+ LoggingHandler(int state, StringBuffer sb) {
+ this(state, sb, true /* handleSelf */);
+ }
+
+ @Override
+ public void enter() {
+ mStringBuffer.append('i');
+ mStringBuffer.append(Integer.toHexString(mState));
+ mStringBuffer.append(';');
+ }
+
+ @Override
+ public void exit() {
+ mStringBuffer.append('o');
+ mStringBuffer.append(Integer.toHexString(mState));
+ mStringBuffer.append(';');
+ }
+
+ @Override
+ public boolean handle(int event, Object param) {
+ if (mHandleSelf) {
+ mStringBuffer.append('h');
+ mStringBuffer.append(Integer.toHexString(mState));
+ mStringBuffer.append(';');
+ }
+ return mHandleSelf;
+ }
+ }
+
+ static class LoggingHandlerTransferInExit extends LoggingHandler {
+ final StateMachine mStateMachine;
+ final int mStateToTransit;
+
+ LoggingHandlerTransferInExit(int state, StringBuffer sb, StateMachine stateMachine,
+ int stateToTransit) {
+ super(state, sb);
+ mStateMachine = stateMachine;
+ mStateToTransit = stateToTransit;
+ }
+
+ @Override
+ public void exit() {
+ super.exit();
+ mStateMachine.transit(mStateToTransit);
+ }
+ }
+
+ @Test
+ public void testStateMachineIsIn() {
+ assertTrue(isIn(0x112, 0x1));
+ assertTrue(isIn(0x112, 0x11));
+ assertTrue(isIn(0x112, 0x112));
+
+ assertFalse(isIn(0x1, 0x112));
+ assertFalse(isIn(0x12, 0x2));
+ }
+
+ @Test
+ public void testStateMachineInitialState() {
+ StateMachine stateMachine = new StateMachine();
+ assertEquals(0, stateMachine.getState());
+
+ stateMachine = new StateMachine(0x23);
+ assertEquals(0x23, stateMachine.getState());
+ }
+
+ @Test
+ public void testStateMachineTransitToChild() {
+ final StringBuffer log = new StringBuffer();
+
+ StateMachine stateMachine = new StateMachine();
+ stateMachine.addStateHandler(0x1, new LoggingHandler(0x1, log));
+ stateMachine.addStateHandler(0x12, new LoggingHandler(0x12, log));
+ stateMachine.addStateHandler(0x123, new LoggingHandler(0x123, log));
+ stateMachine.addStateHandler(0x1233, new LoggingHandler(0x1233, log));
+
+ // 0x0 -> 0x12
+ stateMachine.transit(0x12);
+ assertEquals("i1;i12;", log.toString());
+ assertEquals(0x12, stateMachine.getState());
+
+ // 0x12 -> 0x1233
+ log.setLength(0);
+ stateMachine.transit(0x1233);
+ assertEquals(0x1233, stateMachine.getState());
+ assertEquals("i123;i1233;", log.toString());
+ }
+
+ @Test
+ public void testStateMachineTransitToParent() {
+ final StringBuffer log = new StringBuffer();
+
+ StateMachine stateMachine = new StateMachine(0x253);
+ stateMachine.addStateHandler(0x2, new LoggingHandler(0x2, log));
+ stateMachine.addStateHandler(0x25, new LoggingHandler(0x25, log));
+ stateMachine.addStateHandler(0x253, new LoggingHandler(0x253, log));
+
+ // 0x253 -> 0x2
+ stateMachine.transit(0x2);
+ assertEquals(0x2, stateMachine.getState());
+ assertEquals("o253;o25;", log.toString());
+ }
+
+ @Test
+ public void testStateMachineTransitSelf() {
+ final StringBuffer log = new StringBuffer();
+
+ StateMachine stateMachine = new StateMachine(0x253);
+ stateMachine.addStateHandler(0x2, new LoggingHandler(0x2, log));
+ stateMachine.addStateHandler(0x25, new LoggingHandler(0x25, log));
+ stateMachine.addStateHandler(0x253, new LoggingHandler(0x253, log));
+
+ // 0x253 -> 0x253
+ stateMachine.transit(0x253);
+ assertEquals(0x253, stateMachine.getState());
+ assertEquals("o253;i253;", log.toString());
+ }
+
+ @Test
+ public void testStateMachineTransitGeneral() {
+ final StringBuffer log = new StringBuffer();
+
+ StateMachine stateMachine = new StateMachine(0x1351);
+ stateMachine.addStateHandler(0x1, new LoggingHandler(0x1, log));
+ stateMachine.addStateHandler(0x13, new LoggingHandler(0x13, log));
+ stateMachine.addStateHandler(0x132, new LoggingHandler(0x132, log));
+ stateMachine.addStateHandler(0x1322, new LoggingHandler(0x1322, log));
+ stateMachine.addStateHandler(0x1322, new LoggingHandler(0x1322, log));
+ stateMachine.addStateHandler(0x135, new LoggingHandler(0x135, log));
+ stateMachine.addStateHandler(0x1351, new LoggingHandler(0x1351, log));
+
+ // 0x1351 -> 0x1322
+ // least common ancestor = 0x13
+ stateMachine.transit(0x1322);
+ assertEquals(0x1322, stateMachine.getState());
+ assertEquals("o1351;o135;i132;i1322;", log.toString());
+ }
+
+ @Test
+ public void testStateMachineTriggerStateAction() {
+ final StringBuffer log = new StringBuffer();
+
+ StateMachine stateMachine = new StateMachine(0x253);
+ stateMachine.addStateHandler(0x2, new LoggingHandler(0x2, log));
+ stateMachine.addStateHandler(0x25, new LoggingHandler(0x25, log));
+ stateMachine.addStateHandler(0x253, new LoggingHandler(0x253, log));
+
+ // state 0x253 handles the message itself
+ stateMachine.handle(0, null);
+ assertEquals("h253;", log.toString());
+ }
+
+ @Test
+ public void testStateMachineTriggerStateActionDelegate() {
+ final StringBuffer log = new StringBuffer();
+
+ StateMachine stateMachine = new StateMachine(0x253);
+ stateMachine.addStateHandler(0x2, new LoggingHandler(0x2, log));
+ stateMachine.addStateHandler(0x25, new LoggingHandler(0x25, log));
+ stateMachine.addStateHandler(0x253,
+ new LoggingHandler(0x253, log, false /* handleSelf */));
+
+ // state 0x253 delegate the message handling to its parent state
+ stateMachine.handle(0, null);
+ assertEquals("h25;", log.toString());
+ }
+
+ @Test
+ public void testStateMachineNestedTransition() {
+ final StringBuffer log = new StringBuffer();
+
+ StateMachine stateMachine = new StateMachine(0x25);
+ stateMachine.addStateHandler(0x1, new LoggingHandler(0x1, log));
+
+ // Force transit to state 0x3 in exit()
+ stateMachine.addStateHandler(0x2,
+ new LoggingHandlerTransferInExit(0x2, log, stateMachine, 0x3));
+ stateMachine.addStateHandler(0x25, new LoggingHandler(0x25, log));
+ stateMachine.addStateHandler(0x3, new LoggingHandler(0x3, log));
+
+ stateMachine.transit(0x1);
+ // Start transit to 0x1
+ // 0x25 -> 0x2 [transit(0x3) requested] -> 0x1
+ // 0x1 -> 0x3
+ // Immediately set the status to 0x1, no enter/exit
+ assertEquals("o25;o2;i1;o1;i3;", log.toString());
+ }
+}
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 4fd2b78b6f66..b3a1f2b85f63 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -1864,6 +1864,21 @@ public class UsageStatsService extends SystemService implements
mResponseStatsTracker.dump(idpw);
}
return;
+ } else if ("app-component-usage".equals(arg)) {
+ final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
+ synchronized (mLock) {
+ if (!mLastTimeComponentUsedGlobal.isEmpty()) {
+ ipw.println("App Component Usages:");
+ ipw.increaseIndent();
+ for (String pkg : mLastTimeComponentUsedGlobal.keySet()) {
+ ipw.println("package=" + pkg
+ + " lastUsed=" + UserUsageStatsService.formatDateTime(
+ mLastTimeComponentUsedGlobal.get(pkg), true));
+ }
+ ipw.decreaseIndent();
+ }
+ }
+ return;
} else if (arg != null && !arg.startsWith("-")) {
// Anything else that doesn't start with '-' is a pkg to filter
pkgs.add(arg);
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
index ffdb07b27ad0..b6aed2dbba65 100644
--- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -219,7 +219,7 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser
private static EventLogger sEventLogger;
- private UsbGadgetHal mUsbGadgetHal;
+ private static UsbGadgetHal mUsbGadgetHal;
/**
* Counter for tracking UsbOperation operations.
@@ -1976,7 +1976,7 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser
}
}
- private final class UsbHandlerHal extends UsbHandler {
+ private static final class UsbHandlerHal extends UsbHandler {
private final Object mGadgetProxyLock = new Object();
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordAudioStreamCopier.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordAudioStreamCopier.java
index 76574542da4f..81cd1945589e 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordAudioStreamCopier.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordAudioStreamCopier.java
@@ -19,6 +19,13 @@ package com.android.server.voiceinteraction;
import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.service.voice.HotwordAudioStream.KEY_AUDIO_STREAM_COPY_BUFFER_LENGTH_BYTES;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__AUDIO_EGRESS_CLOSE_ERROR_FROM_SYSTEM;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__AUDIO_EGRESS_EMPTY_AUDIO_STREAM_LIST;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__AUDIO_EGRESS_END;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__AUDIO_EGRESS_ILLEGAL_COPY_BUFFER_SIZE;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__AUDIO_EGRESS_INTERRUPTED_EXCEPTION;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__AUDIO_EGRESS_NO_PERMISSION;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__AUDIO_EGRESS_START;
import static com.android.server.voiceinteraction.HotwordDetectionConnection.DEBUG;
import android.annotation.NonNull;
@@ -59,15 +66,17 @@ final class HotwordAudioStreamCopier {
private static final int MAX_COPY_BUFFER_LENGTH_BYTES = 65_536;
private final AppOpsManager mAppOpsManager;
+ private final int mDetectorType;
private final int mVoiceInteractorUid;
private final String mVoiceInteractorPackageName;
private final String mVoiceInteractorAttributionTag;
private final ExecutorService mExecutorService = Executors.newCachedThreadPool();
- HotwordAudioStreamCopier(@NonNull AppOpsManager appOpsManager,
+ HotwordAudioStreamCopier(@NonNull AppOpsManager appOpsManager, int detectorType,
int voiceInteractorUid, @NonNull String voiceInteractorPackageName,
@NonNull String voiceInteractorAttributionTag) {
mAppOpsManager = appOpsManager;
+ mDetectorType = detectorType;
mVoiceInteractorUid = voiceInteractorUid;
mVoiceInteractorPackageName = voiceInteractorPackageName;
mVoiceInteractorAttributionTag = voiceInteractorAttributionTag;
@@ -89,6 +98,9 @@ final class HotwordAudioStreamCopier {
throws IOException {
List<HotwordAudioStream> audioStreams = result.getAudioStreams();
if (audioStreams.isEmpty()) {
+ HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
+ HOTWORD_DETECTOR_EVENTS__EVENT__AUDIO_EGRESS_EMPTY_AUDIO_STREAM_LIST,
+ mVoiceInteractorUid);
return result;
}
@@ -108,6 +120,9 @@ final class HotwordAudioStreamCopier {
if (metadata.containsKey(KEY_AUDIO_STREAM_COPY_BUFFER_LENGTH_BYTES)) {
copyBufferLength = metadata.getInt(KEY_AUDIO_STREAM_COPY_BUFFER_LENGTH_BYTES, -1);
if (copyBufferLength < 1 || copyBufferLength > MAX_COPY_BUFFER_LENGTH_BYTES) {
+ HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
+ HOTWORD_DETECTOR_EVENTS__EVENT__AUDIO_EGRESS_ILLEGAL_COPY_BUFFER_SIZE,
+ mVoiceInteractorUid);
Slog.w(TAG, "Attempted to set an invalid copy buffer length ("
+ copyBufferLength + ") for: " + audioStream);
copyBufferLength = DEFAULT_COPY_BUFFER_LENGTH_BYTES;
@@ -160,16 +175,25 @@ final class HotwordAudioStreamCopier {
CopyTaskInfo copyTaskInfo = mCopyTaskInfos.get(i);
String streamTaskId = mResultTaskId + "@" + i;
tasks.add(new SingleAudioStreamCopyTask(streamTaskId, copyTaskInfo.mSource,
- copyTaskInfo.mSink, copyTaskInfo.mCopyBufferLength));
+ copyTaskInfo.mSink, copyTaskInfo.mCopyBufferLength, mDetectorType,
+ mVoiceInteractorUid));
}
if (mAppOpsManager.startOpNoThrow(AppOpsManager.OPSTR_RECORD_AUDIO_HOTWORD,
mVoiceInteractorUid, mVoiceInteractorPackageName,
mVoiceInteractorAttributionTag, OP_MESSAGE) == MODE_ALLOWED) {
try {
+ HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
+ HOTWORD_DETECTOR_EVENTS__EVENT__AUDIO_EGRESS_START,
+ mVoiceInteractorUid);
// TODO(b/244599891): Set timeout, close after inactivity
mExecutorService.invokeAll(tasks);
+ HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
+ HOTWORD_DETECTOR_EVENTS__EVENT__AUDIO_EGRESS_END, mVoiceInteractorUid);
} catch (InterruptedException e) {
+ HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
+ HOTWORD_DETECTOR_EVENTS__EVENT__AUDIO_EGRESS_INTERRUPTED_EXCEPTION,
+ mVoiceInteractorUid);
Slog.e(TAG, mResultTaskId + ": Task was interrupted", e);
bestEffortPropagateError(e.getMessage());
} finally {
@@ -178,6 +202,9 @@ final class HotwordAudioStreamCopier {
mVoiceInteractorAttributionTag);
}
} else {
+ HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
+ HOTWORD_DETECTOR_EVENTS__EVENT__AUDIO_EGRESS_NO_PERMISSION,
+ mVoiceInteractorUid);
bestEffortPropagateError(
"Failed to obtain RECORD_AUDIO_HOTWORD permission for voice interactor with"
+ " uid=" + mVoiceInteractorUid
@@ -192,6 +219,9 @@ final class HotwordAudioStreamCopier {
copyTaskInfo.mSource.closeWithError(errorMessage);
copyTaskInfo.mSink.closeWithError(errorMessage);
}
+ HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
+ HOTWORD_DETECTOR_EVENTS__EVENT__AUDIO_EGRESS_CLOSE_ERROR_FROM_SYSTEM,
+ mVoiceInteractorUid);
} catch (IOException e) {
Slog.e(TAG, mResultTaskId + ": Failed to propagate error", e);
}
@@ -204,12 +234,17 @@ final class HotwordAudioStreamCopier {
private final ParcelFileDescriptor mAudioSink;
private final int mCopyBufferLength;
+ private final int mDetectorType;
+ private final int mUid;
+
SingleAudioStreamCopyTask(String streamTaskId, ParcelFileDescriptor audioSource,
- ParcelFileDescriptor audioSink, int copyBufferLength) {
+ ParcelFileDescriptor audioSink, int copyBufferLength, int detectorType, int uid) {
mStreamTaskId = streamTaskId;
mAudioSource = audioSource;
mAudioSink = audioSink;
mCopyBufferLength = copyBufferLength;
+ mDetectorType = detectorType;
+ mUid = uid;
}
@Override
@@ -253,6 +288,8 @@ final class HotwordAudioStreamCopier {
mAudioSource.closeWithError(e.getMessage());
mAudioSink.closeWithError(e.getMessage());
Slog.e(TAG, mStreamTaskId + ": Failed to copy audio stream", e);
+ HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
+ HOTWORD_DETECTOR_EVENTS__EVENT__AUDIO_EGRESS_CLOSE_ERROR_FROM_SYSTEM, mUid);
} finally {
if (fis != null) {
fis.close();
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index 3bcba6c1af65..2ac25b65a082 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -245,7 +245,7 @@ final class HotwordDetectionConnection {
mVoiceInteractionServiceUid = voiceInteractionServiceUid;
mVoiceInteractorIdentity = voiceInteractorIdentity;
mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
- mHotwordAudioStreamCopier = new HotwordAudioStreamCopier(mAppOpsManager,
+ mHotwordAudioStreamCopier = new HotwordAudioStreamCopier(mAppOpsManager, detectorType,
mVoiceInteractorIdentity.uid, mVoiceInteractorIdentity.packageName,
mVoiceInteractorIdentity.attributionTag);
mDetectionComponentName = serviceName;
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/TrustedHotwordDetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/TrustedHotwordDetectorSession.java
index eeafe910e13e..02f5889cc520 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/TrustedHotwordDetectorSession.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/TrustedHotwordDetectorSession.java
@@ -245,7 +245,7 @@ final class TrustedHotwordDetectorSession {
mVoiceInteractionServiceUid = voiceInteractionServiceUid;
mVoiceInteractorIdentity = voiceInteractorIdentity;
mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
- mHotwordAudioStreamCopier = new HotwordAudioStreamCopier(mAppOpsManager,
+ mHotwordAudioStreamCopier = new HotwordAudioStreamCopier(mAppOpsManager, detectorType,
mVoiceInteractorIdentity.uid, mVoiceInteractorIdentity.packageName,
mVoiceInteractorIdentity.attributionTag);
mDetectionComponentName = serviceName;
diff --git a/startop/view_compiler/TEST_MAPPING b/startop/view_compiler/TEST_MAPPING
deleted file mode 100644
index 791e47105ff9..000000000000
--- a/startop/view_compiler/TEST_MAPPING
+++ /dev/null
@@ -1,15 +0,0 @@
-{
- "presubmit": [
- {
- "name": "dex-builder-test"
- },
- {
- "name": "CtsViewTestCases",
- "options": [
- {
- "include-filter": "android.view.cts.PrecompiledLayoutTest"
- }
- ]
- }
- ]
-}
diff --git a/telephony/java/android/service/euicc/EuiccProfileInfo.java b/telephony/java/android/service/euicc/EuiccProfileInfo.java
index 8ec500b4d49d..7eccd1a4482f 100644
--- a/telephony/java/android/service/euicc/EuiccProfileInfo.java
+++ b/telephony/java/android/service/euicc/EuiccProfileInfo.java
@@ -49,7 +49,6 @@ public final class EuiccProfileInfo implements Parcelable {
POLICY_RULE_DO_NOT_DELETE,
POLICY_RULE_DELETE_AFTER_DISABLING
})
- /** @hide */
public @interface PolicyRule {}
/** Once this profile is enabled, it cannot be disabled. */
public static final int POLICY_RULE_DO_NOT_DISABLE = 1;
@@ -66,7 +65,6 @@ public final class EuiccProfileInfo implements Parcelable {
PROFILE_CLASS_OPERATIONAL,
PROFILE_CLASS_UNSET
})
- /** @hide */
public @interface ProfileClass {}
/** Testing profiles */
public static final int PROFILE_CLASS_TESTING = 0;
@@ -87,7 +85,6 @@ public final class EuiccProfileInfo implements Parcelable {
PROFILE_STATE_ENABLED,
PROFILE_STATE_UNSET
})
- /** @hide */
public @interface ProfileState {}
/** Disabled profiles */
public static final int PROFILE_STATE_DISABLED = 0;
diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java
index 6be2f770efe9..7c600b1319dd 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -138,6 +138,12 @@ public class ServiceState implements Parcelable {
*/
public static final int FREQUENCY_RANGE_MMWAVE = 4;
+ /**
+ * Number of frequency ranges.
+ * @hide
+ */
+ public static final int FREQUENCY_RANGE_COUNT = 5;
+
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = "DUPLEX_MODE_",
diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java
index db9dfbb6fe4c..3b84b65891da 100644
--- a/telephony/java/android/telephony/SubscriptionInfo.java
+++ b/telephony/java/android/telephony/SubscriptionInfo.java
@@ -649,6 +649,15 @@ public class SubscriptionInfo implements Parcelable {
}
/**
+ * @return {@code true} if the subscription is from the actively used SIM.
+ *
+ * @hide
+ */
+ public boolean isActive() {
+ return mSimSlotIndex >= 0 || mType == SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM;
+ }
+
+ /**
* Used in scenarios where different subscriptions are bundled as a group.
* It's typically a primary and an opportunistic subscription. (see {@link #isOpportunistic()})
* Such that those subscriptions will have some affiliated behaviors such as opportunistic
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 5c1d4979031f..5244f415e8e2 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -155,6 +155,10 @@ public class SubscriptionManager {
private static final String CACHE_KEY_SLOT_INDEX_PROPERTY =
"cache_key.telephony.get_slot_index";
+ /** The IPC cache key shared by all subscription manager service cacheable properties. */
+ private static final String CACHE_KEY_SUBSCRIPTION_MANAGER_SERVICE_PROPERTY =
+ "cache_key.telephony.subscription_manager_service";
+
/** @hide */
public static final String GET_SIM_SPECIFIC_SETTINGS_METHOD_NAME = "getSimSpecificSettings";
@@ -269,37 +273,72 @@ public class SubscriptionManager {
CACHE_KEY_DEFAULT_SUB_ID_PROPERTY,
INVALID_SUBSCRIPTION_ID);
+ private static VoidPropertyInvalidatedCache<Integer> sGetDefaultSubIdCache =
+ new VoidPropertyInvalidatedCache<>(ISub::getDefaultSubId,
+ CACHE_KEY_SUBSCRIPTION_MANAGER_SERVICE_PROPERTY,
+ INVALID_SUBSCRIPTION_ID);
+
private static VoidPropertyInvalidatedCache<Integer> sDefaultDataSubIdCache =
new VoidPropertyInvalidatedCache<>(ISub::getDefaultDataSubId,
CACHE_KEY_DEFAULT_DATA_SUB_ID_PROPERTY,
INVALID_SUBSCRIPTION_ID);
+ private static VoidPropertyInvalidatedCache<Integer> sGetDefaultDataSubIdCache =
+ new VoidPropertyInvalidatedCache<>(ISub::getDefaultDataSubId,
+ CACHE_KEY_SUBSCRIPTION_MANAGER_SERVICE_PROPERTY,
+ INVALID_SUBSCRIPTION_ID);
+
private static VoidPropertyInvalidatedCache<Integer> sDefaultSmsSubIdCache =
new VoidPropertyInvalidatedCache<>(ISub::getDefaultSmsSubId,
CACHE_KEY_DEFAULT_SMS_SUB_ID_PROPERTY,
INVALID_SUBSCRIPTION_ID);
+ private static VoidPropertyInvalidatedCache<Integer> sGetDefaultSmsSubIdCache =
+ new VoidPropertyInvalidatedCache<>(ISub::getDefaultSmsSubId,
+ CACHE_KEY_SUBSCRIPTION_MANAGER_SERVICE_PROPERTY,
+ INVALID_SUBSCRIPTION_ID);
+
private static VoidPropertyInvalidatedCache<Integer> sActiveDataSubIdCache =
new VoidPropertyInvalidatedCache<>(ISub::getActiveDataSubscriptionId,
CACHE_KEY_ACTIVE_DATA_SUB_ID_PROPERTY,
INVALID_SUBSCRIPTION_ID);
+ private static VoidPropertyInvalidatedCache<Integer> sGetActiveDataSubscriptionIdCache =
+ new VoidPropertyInvalidatedCache<>(ISub::getActiveDataSubscriptionId,
+ CACHE_KEY_SUBSCRIPTION_MANAGER_SERVICE_PROPERTY,
+ INVALID_SUBSCRIPTION_ID);
+
private static IntegerPropertyInvalidatedCache<Integer> sSlotIndexCache =
new IntegerPropertyInvalidatedCache<>(ISub::getSlotIndex,
CACHE_KEY_SLOT_INDEX_PROPERTY,
INVALID_SIM_SLOT_INDEX);
+ private static IntegerPropertyInvalidatedCache<Integer> sGetSlotIndexCache =
+ new IntegerPropertyInvalidatedCache<>(ISub::getSlotIndex,
+ CACHE_KEY_SUBSCRIPTION_MANAGER_SERVICE_PROPERTY,
+ INVALID_SIM_SLOT_INDEX);
+
private static IntegerPropertyInvalidatedCache<Integer> sSubIdCache =
new IntegerPropertyInvalidatedCache<>(ISub::getSubId,
CACHE_KEY_SLOT_INDEX_PROPERTY,
INVALID_SUBSCRIPTION_ID);
+ private static IntegerPropertyInvalidatedCache<Integer> sGetSubIdCache =
+ new IntegerPropertyInvalidatedCache<>(ISub::getSubId,
+ CACHE_KEY_SUBSCRIPTION_MANAGER_SERVICE_PROPERTY,
+ INVALID_SUBSCRIPTION_ID);
+
/** Cache depends on getDefaultSubId, so we use the defaultSubId cache key */
private static IntegerPropertyInvalidatedCache<Integer> sPhoneIdCache =
new IntegerPropertyInvalidatedCache<>(ISub::getPhoneId,
CACHE_KEY_DEFAULT_SUB_ID_PROPERTY,
INVALID_PHONE_INDEX);
+ private static IntegerPropertyInvalidatedCache<Integer> sGetPhoneIdCache =
+ new IntegerPropertyInvalidatedCache<>(ISub::getPhoneId,
+ CACHE_KEY_SUBSCRIPTION_MANAGER_SERVICE_PROPERTY,
+ INVALID_PHONE_INDEX);
+
/**
* Generates a content {@link Uri} used to receive updates on simInfo change
* on the given subscriptionId
@@ -1298,6 +1337,8 @@ public class SubscriptionManager {
private final Context mContext;
+ private static boolean sIsSubscriptionManagerServiceEnabled = false;
+
// Cache of Resource that has been created in getResourcesForSubId. Key is a Pair containing
// the Context and subId.
private static final Map<Pair<Context, Integer>, Resources> sResourcesCache =
@@ -1383,6 +1424,19 @@ public class SubscriptionManager {
public SubscriptionManager(Context context) {
if (DBG) logd("SubscriptionManager created");
mContext = context;
+
+ sIsSubscriptionManagerServiceEnabled = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_using_subscription_manager_service);
+ }
+
+ /**
+ * @return {@code true} if the new subscription manager service is used. This is temporary and
+ * will be removed before Android 14 release.
+ *
+ * @hide
+ */
+ public static boolean isSubscriptionManagerServiceEnabled() {
+ return sIsSubscriptionManagerServiceEnabled;
}
private NetworkPolicyManager getNetworkPolicyManager() {
@@ -1712,8 +1766,7 @@ public class SubscriptionManager {
*
* <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
* or that the calling app has carrier privileges (see
- * {@link TelephonyManager#hasCarrierPrivileges}). In the latter case, only records accessible
- * to the calling app are returned.
+ * {@link TelephonyManager#hasCarrierPrivileges}).
*
* @return Sorted list of the currently {@link SubscriptionInfo} records available on the device.
* <ul>
@@ -1731,7 +1784,6 @@ public class SubscriptionManager {
* </li>
* </ul>
*/
- @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
@RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
public List<SubscriptionInfo> getActiveSubscriptionInfoList() {
return getActiveSubscriptionInfoList(/* userVisibleonly */true);
@@ -1935,17 +1987,12 @@ public class SubscriptionManager {
}
/**
+ * Get the active subscription count.
*
- * Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
- * or that the calling app has carrier privileges (see
- * {@link TelephonyManager#hasCarrierPrivileges}). In the latter case, the count will include
- * only those subscriptions accessible to the caller.
+ * @return The current number of active subscriptions.
*
- * @return the current number of active subscriptions. There is no guarantee the value
- * returned by this method will be the same as the length of the list returned by
- * {@link #getActiveSubscriptionInfoList}.
+ * @see #getActiveSubscriptionInfoList()
*/
- @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
@RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
public int getActiveSubscriptionInfoCount() {
int result = 0;
@@ -2085,7 +2132,7 @@ public class SubscriptionManager {
/**
* Set SIM icon tint color for subscription ID
* @param tint the RGB value of icon tint color of the SIM
- * @param subId the unique Subscritpion ID in database
+ * @param subId the unique subscription ID in database
* @return the number of records updated
* @hide
*/
@@ -2093,7 +2140,7 @@ public class SubscriptionManager {
public int setIconTint(@ColorInt int tint, int subId) {
if (VDBG) logd("[setIconTint]+ tint:" + tint + " subId:" + subId);
return setSubscriptionPropertyHelper(subId, "setIconTint",
- (iSub)-> iSub.setIconTint(tint, subId)
+ (iSub)-> iSub.setIconTint(subId, tint)
);
}
@@ -2158,6 +2205,7 @@ public class SubscriptionManager {
* subscriptionId doesn't have an associated slot index.
*/
public static int getSlotIndex(int subscriptionId) {
+ if (isSubscriptionManagerServiceEnabled()) return sGetSlotIndexCache.query(subscriptionId);
return sSlotIndexCache.query(subscriptionId);
}
@@ -2207,12 +2255,14 @@ public class SubscriptionManager {
return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
}
+ if (isSubscriptionManagerServiceEnabled()) return sGetSubIdCache.query(slotIndex);
return sSubIdCache.query(slotIndex);
}
/** @hide */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
public static int getPhoneId(int subId) {
+ if (isSubscriptionManagerServiceEnabled()) return sGetPhoneIdCache.query(subId);
return sPhoneIdCache.query(subId);
}
@@ -2234,6 +2284,7 @@ public class SubscriptionManager {
* @return the "system" default subscription id.
*/
public static int getDefaultSubscriptionId() {
+ if (isSubscriptionManagerServiceEnabled()) return sGetDefaultSubIdCache.query(null);
return sDefaultSubIdCache.query(null);
}
@@ -2322,6 +2373,7 @@ public class SubscriptionManager {
* @return the default SMS subscription Id.
*/
public static int getDefaultSmsSubscriptionId() {
+ if (isSubscriptionManagerServiceEnabled()) return sGetDefaultSmsSubIdCache.query(null);
return sDefaultSmsSubIdCache.query(null);
}
@@ -2356,6 +2408,7 @@ public class SubscriptionManager {
* @return the default data subscription Id.
*/
public static int getDefaultDataSubscriptionId() {
+ if (isSubscriptionManagerServiceEnabled()) return sGetDefaultDataSubIdCache.query(null);
return sDefaultDataSubIdCache.query(null);
}
@@ -3819,6 +3872,9 @@ public class SubscriptionManager {
* SubscriptionManager.INVALID_SUBSCRIPTION_ID if not.
*/
public static int getActiveDataSubscriptionId() {
+ if (isSubscriptionManagerServiceEnabled()) {
+ return sGetActiveDataSubscriptionIdCache.query(null);
+ }
return sActiveDataSubIdCache.query(null);
}
@@ -3862,6 +3918,11 @@ public class SubscriptionManager {
PropertyInvalidatedCache.invalidateCache(CACHE_KEY_SLOT_INDEX_PROPERTY);
}
+ /** @hide */
+ public static void invalidateSubscriptionManagerServiceCaches() {
+ PropertyInvalidatedCache.invalidateCache(CACHE_KEY_SUBSCRIPTION_MANAGER_SERVICE_PROPERTY);
+ }
+
/**
* Allows a test process to disable client-side caching operations.
*
@@ -3873,7 +3934,16 @@ public class SubscriptionManager {
sActiveDataSubIdCache.disableLocal();
sDefaultSmsSubIdCache.disableLocal();
sSlotIndexCache.disableLocal();
+ sSubIdCache.disableLocal();
sPhoneIdCache.disableLocal();
+
+ sGetDefaultSubIdCache.disableLocal();
+ sGetDefaultDataSubIdCache.disableLocal();
+ sGetActiveDataSubscriptionIdCache.disableLocal();
+ sGetDefaultSmsSubIdCache.disableLocal();
+ sGetSlotIndexCache.disableLocal();
+ sGetSubIdCache.disableLocal();
+ sGetPhoneIdCache.disableLocal();
}
/**
@@ -3886,7 +3956,16 @@ public class SubscriptionManager {
sActiveDataSubIdCache.clear();
sDefaultSmsSubIdCache.clear();
sSlotIndexCache.clear();
+ sSubIdCache.clear();
sPhoneIdCache.clear();
+
+ sGetDefaultSubIdCache.clear();
+ sGetDefaultDataSubIdCache.clear();
+ sGetActiveDataSubscriptionIdCache.clear();
+ sGetDefaultSmsSubIdCache.clear();
+ sGetSlotIndexCache.clear();
+ sGetSubIdCache.clear();
+ sGetPhoneIdCache.clear();
}
/**
diff --git a/telephony/java/android/telephony/data/DataProfile.java b/telephony/java/android/telephony/data/DataProfile.java
index 5e111639b100..f346b9208bcd 100644
--- a/telephony/java/android/telephony/data/DataProfile.java
+++ b/telephony/java/android/telephony/data/DataProfile.java
@@ -147,7 +147,7 @@ public final class DataProfile implements Parcelable {
if (mApnSetting != null) {
return mApnSetting.getProtocol();
}
- return ApnSetting.PROTOCOL_IP;
+ return ApnSetting.PROTOCOL_IPV4V6;
}
/**
diff --git a/telephony/java/android/telephony/euicc/EuiccCardManager.java b/telephony/java/android/telephony/euicc/EuiccCardManager.java
index e61d1e6842a7..b18eaa5334c6 100644
--- a/telephony/java/android/telephony/euicc/EuiccCardManager.java
+++ b/telephony/java/android/telephony/euicc/EuiccCardManager.java
@@ -75,7 +75,6 @@ public class EuiccCardManager {
CANCEL_REASON_TIMEOUT,
CANCEL_REASON_PPR_NOT_ALLOWED
})
- /** @hide */
public @interface CancelReason {
}
@@ -105,7 +104,6 @@ public class EuiccCardManager {
RESET_OPTION_DELETE_FIELD_LOADED_TEST_PROFILES,
RESET_OPTION_RESET_DEFAULT_SMDP_ADDRESS
})
- /** @hide */
public @interface ResetOption {
}
diff --git a/telephony/java/android/telephony/euicc/EuiccNotification.java b/telephony/java/android/telephony/euicc/EuiccNotification.java
index c348cff25052..be0048f73bf4 100644
--- a/telephony/java/android/telephony/euicc/EuiccNotification.java
+++ b/telephony/java/android/telephony/euicc/EuiccNotification.java
@@ -44,7 +44,6 @@ public final class EuiccNotification implements Parcelable {
EVENT_DISABLE,
EVENT_DELETE
})
- /** @hide */
public @interface Event {}
/** A profile is downloaded and installed. */
diff --git a/telephony/java/android/telephony/euicc/EuiccRulesAuthTable.java b/telephony/java/android/telephony/euicc/EuiccRulesAuthTable.java
index d5a05ae2dbb8..1c6b6b6e83fc 100644
--- a/telephony/java/android/telephony/euicc/EuiccRulesAuthTable.java
+++ b/telephony/java/android/telephony/euicc/EuiccRulesAuthTable.java
@@ -42,7 +42,6 @@ public final class EuiccRulesAuthTable implements Parcelable {
@IntDef(flag = true, prefix = { "POLICY_RULE_FLAG_" }, value = {
POLICY_RULE_FLAG_CONSENT_REQUIRED
})
- /** @hide */
public @interface PolicyRuleFlag {}
/** User consent is required to install the profile. */
diff --git a/telephony/java/android/telephony/ims/feature/MmTelFeature.java b/telephony/java/android/telephony/ims/feature/MmTelFeature.java
index d776928965e5..8184424fef26 100644
--- a/telephony/java/android/telephony/ims/feature/MmTelFeature.java
+++ b/telephony/java/android/telephony/ims/feature/MmTelFeature.java
@@ -700,6 +700,7 @@ public class MmTelFeature extends ImsFeature {
throw new IllegalStateException("Session is not available.");
}
try {
+ c.setDefaultExecutor(MmTelFeature.this.mExecutor);
listener.onIncomingCall(c.getServiceImpl(), extras);
} catch (RemoteException e) {
throw new RuntimeException(e);
diff --git a/telephony/java/com/android/internal/telephony/ISub.aidl b/telephony/java/com/android/internal/telephony/ISub.aidl
index 5173405ac17d..e9cea6843a82 100644
--- a/telephony/java/com/android/internal/telephony/ISub.aidl
+++ b/telephony/java/com/android/internal/telephony/ISub.aidl
@@ -135,11 +135,11 @@ interface ISub {
/**
* Set SIM icon tint color by simInfo index
- * @param tint the icon tint color of the SIM
* @param subId the unique SubscriptionInfo index in database
+ * @param tint the icon tint color of the SIM
* @return the number of records updated
*/
- int setIconTint(int tint, int subId);
+ int setIconTint(int subId, int tint);
/**
* Set display name by simInfo index with name source
diff --git a/tests/FlickerTests/AndroidTest.xml b/tests/FlickerTests/AndroidTest.xml
index a7d6a018c9c7..84781b473214 100644
--- a/tests/FlickerTests/AndroidTest.xml
+++ b/tests/FlickerTests/AndroidTest.xml
@@ -19,6 +19,8 @@
<option name="run-command" value="pm disable com.google.android.internal.betterbug" />
<!-- restart launcher to activate TAPL -->
<option name="run-command" value="setprop ro.test_harness 1 ; am force-stop com.google.android.apps.nexuslauncher" />
+ <!-- Ensure output directory is empty at the start -->
+ <option name="run-command" value="rm -rf /sdcard/flicker" />
</target_preparer>
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true"/>
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
index 3f6a75d74d36..a4609f7510e3 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
@@ -20,7 +20,7 @@ import android.app.Instrumentation
import android.platform.test.annotations.Presubmit
import androidx.test.platform.app.InstrumentationRegistry
import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.junit.FlickerBuilderProvider
import com.android.server.wm.traces.common.ComponentNameMatcher
import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
import org.junit.Assume
@@ -34,12 +34,12 @@ import org.junit.Test
abstract class BaseTest
@JvmOverloads
constructor(
- protected val testSpec: FlickerTestParameter,
+ protected val flicker: FlickerTest,
protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation(),
protected val tapl: LauncherInstrumentation = LauncherInstrumentation()
) {
init {
- testSpec.setIsTablet(
+ flicker.scenario.setIsTablet(
WindowManagerStateHelper(instrumentation, clearCacheAfterParsing = false)
.currentState
.wmState
@@ -58,13 +58,13 @@ constructor(
@FlickerBuilderProvider
fun buildFlicker(): FlickerBuilder {
return FlickerBuilder(instrumentation).apply {
- setup { testSpec.setIsTablet(wmHelper.currentState.wmState.isTablet) }
+ setup { flicker.scenario.setIsTablet(wmHelper.currentState.wmState.isTablet) }
transition()
}
}
/** Checks that all parts of the screen are covered during the transition */
- @Presubmit @Test open fun entireScreenCovered() = testSpec.entireScreenCovered()
+ @Presubmit @Test open fun entireScreenCovered() = flicker.entireScreenCovered()
/**
* Checks that the [ComponentNameMatcher.NAV_BAR] layer is visible during the whole transition
@@ -74,8 +74,8 @@ constructor(
@Presubmit
@Test
open fun navBarLayerIsVisibleAtStartAndEnd() {
- Assume.assumeFalse(testSpec.isTablet)
- testSpec.navBarLayerIsVisibleAtStartAndEnd()
+ Assume.assumeFalse(flicker.scenario.isTablet)
+ flicker.navBarLayerIsVisibleAtStartAndEnd()
}
/**
@@ -87,8 +87,8 @@ constructor(
@Presubmit
@Test
open fun navBarLayerPositionAtStartAndEnd() {
- Assume.assumeFalse(testSpec.isTablet)
- testSpec.navBarLayerPositionAtStartAndEnd()
+ Assume.assumeFalse(flicker.scenario.isTablet)
+ flicker.navBarLayerPositionAtStartAndEnd()
}
/**
@@ -99,8 +99,8 @@ constructor(
@Presubmit
@Test
open fun navBarWindowIsAlwaysVisible() {
- Assume.assumeFalse(testSpec.isTablet)
- testSpec.navBarWindowIsAlwaysVisible()
+ Assume.assumeFalse(flicker.scenario.isTablet)
+ flicker.navBarWindowIsAlwaysVisible()
}
/**
@@ -112,8 +112,8 @@ constructor(
@Presubmit
@Test
open fun taskBarLayerIsVisibleAtStartAndEnd() {
- Assume.assumeTrue(testSpec.isTablet)
- testSpec.taskBarLayerIsVisibleAtStartAndEnd()
+ Assume.assumeTrue(flicker.scenario.isTablet)
+ flicker.taskBarLayerIsVisibleAtStartAndEnd()
}
/**
@@ -124,8 +124,8 @@ constructor(
@Presubmit
@Test
open fun taskBarWindowIsAlwaysVisible() {
- Assume.assumeTrue(testSpec.isTablet)
- testSpec.taskBarWindowIsAlwaysVisible()
+ Assume.assumeTrue(flicker.scenario.isTablet)
+ flicker.taskBarWindowIsAlwaysVisible()
}
/**
@@ -134,8 +134,7 @@ constructor(
*/
@Presubmit
@Test
- open fun statusBarLayerIsVisibleAtStartAndEnd() =
- testSpec.statusBarLayerIsVisibleAtStartAndEnd()
+ open fun statusBarLayerIsVisibleAtStartAndEnd() = flicker.statusBarLayerIsVisibleAtStartAndEnd()
/**
* Checks the position of the [ComponentNameMatcher.STATUS_BAR] at the start and end of the
@@ -143,7 +142,7 @@ constructor(
*/
@Presubmit
@Test
- open fun statusBarLayerPositionAtStartAndEnd() = testSpec.statusBarLayerPositionAtStartAndEnd()
+ open fun statusBarLayerPositionAtStartAndEnd() = flicker.statusBarLayerPositionAtStartAndEnd()
/**
* Checks that the [ComponentNameMatcher.STATUS_BAR] window is visible during the whole
@@ -151,7 +150,7 @@ constructor(
*/
@Presubmit
@Test
- open fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible()
+ open fun statusBarWindowIsAlwaysVisible() = flicker.statusBarWindowIsAlwaysVisible()
/**
* Checks that all layers that are visible on the trace, are visible for at least 2 consecutive
@@ -160,7 +159,7 @@ constructor(
@Presubmit
@Test
open fun visibleLayersShownMoreThanOneConsecutiveEntry() {
- testSpec.assertLayers { this.visibleLayersShownMoreThanOneConsecutiveEntry() }
+ flicker.assertLayers { this.visibleLayersShownMoreThanOneConsecutiveEntry() }
}
/**
@@ -170,19 +169,23 @@ constructor(
@Presubmit
@Test
open fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
- testSpec.assertWm { this.visibleWindowsShownMoreThanOneConsecutiveEntry() }
+ flicker.assertWm { this.visibleWindowsShownMoreThanOneConsecutiveEntry() }
}
open fun cujCompleted() {
entireScreenCovered()
- navBarLayerIsVisibleAtStartAndEnd()
- navBarWindowIsAlwaysVisible()
- taskBarLayerIsVisibleAtStartAndEnd()
- taskBarWindowIsAlwaysVisible()
statusBarLayerIsVisibleAtStartAndEnd()
statusBarLayerPositionAtStartAndEnd()
statusBarWindowIsAlwaysVisible()
visibleLayersShownMoreThanOneConsecutiveEntry()
visibleWindowsShownMoreThanOneConsecutiveEntry()
+
+ if (flicker.scenario.isTablet) {
+ taskBarLayerIsVisibleAtStartAndEnd()
+ taskBarWindowIsAlwaysVisible()
+ } else {
+ navBarLayerIsVisibleAtStartAndEnd()
+ navBarWindowIsAlwaysVisible()
+ }
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
index bbffd0881775..f9a245a14e29 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
@@ -27,7 +27,7 @@ import com.android.server.wm.traces.common.IComponentNameMatcher
* Checks that [ComponentNameMatcher.STATUS_BAR] window is visible and above the app windows in all
* WM trace entries
*/
-fun FlickerTestParameter.statusBarWindowIsAlwaysVisible() {
+fun FlickerTest.statusBarWindowIsAlwaysVisible() {
assertWm { this.isAboveAppWindowVisible(ComponentNameMatcher.STATUS_BAR) }
}
@@ -35,7 +35,7 @@ fun FlickerTestParameter.statusBarWindowIsAlwaysVisible() {
* Checks that [ComponentNameMatcher.NAV_BAR] window is visible and above the app windows in all WM
* trace entries
*/
-fun FlickerTestParameter.navBarWindowIsAlwaysVisible() {
+fun FlickerTest.navBarWindowIsAlwaysVisible() {
assertWm { this.isAboveAppWindowVisible(ComponentNameMatcher.NAV_BAR) }
}
@@ -43,7 +43,7 @@ fun FlickerTestParameter.navBarWindowIsAlwaysVisible() {
* Checks that [ComponentNameMatcher.NAV_BAR] window is visible and above the app windows at the
* start and end of the WM trace
*/
-fun FlickerTestParameter.navBarWindowIsVisibleAtStartAndEnd() {
+fun FlickerTest.navBarWindowIsVisibleAtStartAndEnd() {
this.navBarWindowIsVisibleAtStart()
this.navBarWindowIsVisibleAtEnd()
}
@@ -52,7 +52,7 @@ fun FlickerTestParameter.navBarWindowIsVisibleAtStartAndEnd() {
* Checks that [ComponentNameMatcher.NAV_BAR] window is visible and above the app windows at the
* start of the WM trace
*/
-fun FlickerTestParameter.navBarWindowIsVisibleAtStart() {
+fun FlickerTest.navBarWindowIsVisibleAtStart() {
assertWmStart { this.isAboveAppWindowVisible(ComponentNameMatcher.NAV_BAR) }
}
@@ -60,7 +60,7 @@ fun FlickerTestParameter.navBarWindowIsVisibleAtStart() {
* Checks that [ComponentNameMatcher.NAV_BAR] window is visible and above the app windows at the end
* of the WM trace
*/
-fun FlickerTestParameter.navBarWindowIsVisibleAtEnd() {
+fun FlickerTest.navBarWindowIsVisibleAtEnd() {
assertWmEnd { this.isAboveAppWindowVisible(ComponentNameMatcher.NAV_BAR) }
}
@@ -68,7 +68,7 @@ fun FlickerTestParameter.navBarWindowIsVisibleAtEnd() {
* Checks that [ComponentNameMatcher.TASK_BAR] window is visible and above the app windows in all WM
* trace entries
*/
-fun FlickerTestParameter.taskBarWindowIsAlwaysVisible() {
+fun FlickerTest.taskBarWindowIsAlwaysVisible() {
assertWm { this.isAboveAppWindowVisible(ComponentNameMatcher.TASK_BAR) }
}
@@ -76,7 +76,7 @@ fun FlickerTestParameter.taskBarWindowIsAlwaysVisible() {
* Checks that [ComponentNameMatcher.TASK_BAR] window is visible and above the app windows in all WM
* trace entries
*/
-fun FlickerTestParameter.taskBarWindowIsVisibleAtEnd() {
+fun FlickerTest.taskBarWindowIsVisibleAtEnd() {
assertWmEnd { this.isAboveAppWindowVisible(ComponentNameMatcher.TASK_BAR) }
}
@@ -90,7 +90,7 @@ fun FlickerTestParameter.taskBarWindowIsVisibleAtEnd() {
* @param allStates if all states should be checked, othersie, just initial and final
*/
@JvmOverloads
-fun FlickerTestParameter.entireScreenCovered(allStates: Boolean = true) {
+fun FlickerTest.entireScreenCovered(allStates: Boolean = true) {
if (allStates) {
assertLayers {
this.invoke("entireScreenCovered") { entry ->
@@ -114,19 +114,19 @@ fun FlickerTestParameter.entireScreenCovered(allStates: Boolean = true) {
}
/** Checks that [ComponentNameMatcher.NAV_BAR] layer is visible at the start of the SF trace */
-fun FlickerTestParameter.navBarLayerIsVisibleAtStart() {
+fun FlickerTest.navBarLayerIsVisibleAtStart() {
assertLayersStart { this.isVisible(ComponentNameMatcher.NAV_BAR) }
}
/** Checks that [ComponentNameMatcher.NAV_BAR] layer is visible at the end of the SF trace */
-fun FlickerTestParameter.navBarLayerIsVisibleAtEnd() {
+fun FlickerTest.navBarLayerIsVisibleAtEnd() {
assertLayersEnd { this.isVisible(ComponentNameMatcher.NAV_BAR) }
}
/**
* Checks that [ComponentNameMatcher.NAV_BAR] layer is visible at the start and end of the SF trace
*/
-fun FlickerTestParameter.navBarLayerIsVisibleAtStartAndEnd() {
+fun FlickerTest.navBarLayerIsVisibleAtStartAndEnd() {
this.navBarLayerIsVisibleAtStart()
this.navBarLayerIsVisibleAtEnd()
}
@@ -134,18 +134,18 @@ fun FlickerTestParameter.navBarLayerIsVisibleAtStartAndEnd() {
/**
* Checks that [ComponentNameMatcher.TASK_BAR] layer is visible at the start and end of the SF trace
*/
-fun FlickerTestParameter.taskBarLayerIsVisibleAtStartAndEnd() {
+fun FlickerTest.taskBarLayerIsVisibleAtStartAndEnd() {
this.taskBarLayerIsVisibleAtStart()
this.taskBarLayerIsVisibleAtEnd()
}
/** Checks that [ComponentNameMatcher.TASK_BAR] layer is visible at the start of the SF trace */
-fun FlickerTestParameter.taskBarLayerIsVisibleAtStart() {
+fun FlickerTest.taskBarLayerIsVisibleAtStart() {
assertLayersStart { this.isVisible(ComponentNameMatcher.TASK_BAR) }
}
/** Checks that [ComponentNameMatcher.TASK_BAR] layer is visible at the end of the SF trace */
-fun FlickerTestParameter.taskBarLayerIsVisibleAtEnd() {
+fun FlickerTest.taskBarLayerIsVisibleAtEnd() {
assertLayersEnd { this.isVisible(ComponentNameMatcher.TASK_BAR) }
}
@@ -153,7 +153,7 @@ fun FlickerTestParameter.taskBarLayerIsVisibleAtEnd() {
* Checks that [ComponentNameMatcher.STATUS_BAR] layer is visible at the start and end of the SF
* trace
*/
-fun FlickerTestParameter.statusBarLayerIsVisibleAtStartAndEnd() {
+fun FlickerTest.statusBarLayerIsVisibleAtStartAndEnd() {
assertLayersStart { this.isVisible(ComponentNameMatcher.STATUS_BAR) }
assertLayersEnd { this.isVisible(ComponentNameMatcher.STATUS_BAR) }
}
@@ -162,12 +162,14 @@ fun FlickerTestParameter.statusBarLayerIsVisibleAtStartAndEnd() {
* Asserts that the [ComponentNameMatcher.NAV_BAR] layer is at the correct position at the start of
* the SF trace
*/
-fun FlickerTestParameter.navBarLayerPositionAtStart() {
+fun FlickerTest.navBarLayerPositionAtStart() {
assertLayersStart {
val display =
this.entry.displays.firstOrNull { !it.isVirtual } ?: error("There is no display!")
this.visibleRegion(ComponentNameMatcher.NAV_BAR)
- .coversExactly(WindowUtils.getNavigationBarPosition(display, isGesturalNavigation))
+ .coversExactly(
+ WindowUtils.getNavigationBarPosition(display, scenario.isGesturalNavigation)
+ )
}
}
@@ -175,13 +177,15 @@ fun FlickerTestParameter.navBarLayerPositionAtStart() {
* Asserts that the [ComponentNameMatcher.NAV_BAR] layer is at the correct position at the end of
* the SF trace
*/
-fun FlickerTestParameter.navBarLayerPositionAtEnd() {
+fun FlickerTest.navBarLayerPositionAtEnd() {
assertLayersEnd {
val display =
this.entry.displays.minByOrNull { it.id }
?: throw RuntimeException("There is no display!")
this.visibleRegion(ComponentNameMatcher.NAV_BAR)
- .coversExactly(WindowUtils.getNavigationBarPosition(display, isGesturalNavigation))
+ .coversExactly(
+ WindowUtils.getNavigationBarPosition(display, scenario.isGesturalNavigation)
+ )
}
}
@@ -189,7 +193,7 @@ fun FlickerTestParameter.navBarLayerPositionAtEnd() {
* Asserts that the [ComponentNameMatcher.NAV_BAR] layer is at the correct position at the start and
* end of the SF trace
*/
-fun FlickerTestParameter.navBarLayerPositionAtStartAndEnd() {
+fun FlickerTest.navBarLayerPositionAtStartAndEnd() {
navBarLayerPositionAtStart()
navBarLayerPositionAtEnd()
}
@@ -198,7 +202,7 @@ fun FlickerTestParameter.navBarLayerPositionAtStartAndEnd() {
* Asserts that the [ComponentNameMatcher.STATUS_BAR] layer is at the correct position at the start
* of the SF trace
*/
-fun FlickerTestParameter.statusBarLayerPositionAtStart() {
+fun FlickerTest.statusBarLayerPositionAtStart() {
assertLayersStart {
val display =
this.entry.displays.minByOrNull { it.id }
@@ -212,7 +216,7 @@ fun FlickerTestParameter.statusBarLayerPositionAtStart() {
* Asserts that the [ComponentNameMatcher.STATUS_BAR] layer is at the correct position at the end of
* the SF trace
*/
-fun FlickerTestParameter.statusBarLayerPositionAtEnd() {
+fun FlickerTest.statusBarLayerPositionAtEnd() {
assertLayersEnd {
val display =
this.entry.displays.minByOrNull { it.id }
@@ -226,7 +230,7 @@ fun FlickerTestParameter.statusBarLayerPositionAtEnd() {
* Asserts that the [ComponentNameMatcher.STATUS_BAR] layer is at the correct position at the start
* and end of the SF trace
*/
-fun FlickerTestParameter.statusBarLayerPositionAtStartAndEnd() {
+fun FlickerTest.statusBarLayerPositionAtStartAndEnd() {
statusBarLayerPositionAtStart()
statusBarLayerPositionAtEnd()
}
@@ -235,9 +239,7 @@ fun FlickerTestParameter.statusBarLayerPositionAtStartAndEnd() {
* Asserts that the visibleRegion of the [ComponentNameMatcher.SNAPSHOT] layer can cover the
* visibleRegion of the given app component exactly
*/
-fun FlickerTestParameter.snapshotStartingWindowLayerCoversExactlyOnApp(
- component: IComponentNameMatcher
-) {
+fun FlickerTest.snapshotStartingWindowLayerCoversExactlyOnApp(component: IComponentNameMatcher) {
assertLayers {
invoke("snapshotStartingWindowLayerCoversExactlyOnApp") {
val snapshotLayers =
@@ -291,7 +293,7 @@ fun FlickerTestParameter.snapshotStartingWindowLayerCoversExactlyOnApp(
* otherwise we won't and the layer must appear immediately.
* ```
*/
-fun FlickerTestParameter.replacesLayer(
+fun FlickerTest.replacesLayer(
originalLayer: IComponentNameMatcher,
newLayer: IComponentNameMatcher,
ignoreEntriesWithRotationLayer: Boolean = false,
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/ActivityEmbeddingTestBase.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/ActivityEmbeddingTestBase.kt
index 4cf669148497..b7bdeeb7d834 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/ActivityEmbeddingTestBase.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/ActivityEmbeddingTestBase.kt
@@ -17,11 +17,11 @@
package com.android.server.wm.flicker.activityembedding
import com.android.server.wm.flicker.BaseTest
-import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper
import org.junit.Before
-abstract class ActivityEmbeddingTestBase(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
+abstract class ActivityEmbeddingTestBase(flicker: FlickerTest) : BaseTest(flicker) {
val testApp = ActivityEmbeddingAppHelper(instrumentation)
@Before
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingPlaceholderSplit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingPlaceholderSplit.kt
index b23fb5a284c1..ea6772981436 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingPlaceholderSplit.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingPlaceholderSplit.kt
@@ -17,14 +17,12 @@
package com.android.server.wm.flicker.activityembedding
import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import android.view.WindowManagerPolicyConstants
import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -41,8 +39,8 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class OpenActivityEmbeddingPlaceholderSplit(testSpec: FlickerTestParameter) :
- ActivityEmbeddingTestBase(testSpec) {
+class OpenActivityEmbeddingPlaceholderSplit(flicker: FlickerTest) :
+ ActivityEmbeddingTestBase(flicker) {
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit = {
@@ -60,7 +58,7 @@ class OpenActivityEmbeddingPlaceholderSplit(testSpec: FlickerTestParameter) :
@Presubmit
@Test
fun mainActivityBecomesInvisible() {
- testSpec.assertLayers {
+ flicker.assertLayers {
isVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
.then()
.isInvisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
@@ -70,12 +68,12 @@ class OpenActivityEmbeddingPlaceholderSplit(testSpec: FlickerTestParameter) :
@Presubmit
@Test
fun placeholderSplitBecomesVisible() {
- testSpec.assertLayers {
+ flicker.assertLayers {
isInvisible(ActivityEmbeddingAppHelper.PLACEHOLDER_PRIMARY_COMPONENT)
.then()
.isVisible(ActivityEmbeddingAppHelper.PLACEHOLDER_PRIMARY_COMPONENT)
}
- testSpec.assertLayers {
+ flicker.assertLayers {
isInvisible(ActivityEmbeddingAppHelper.PLACEHOLDER_SECONDARY_COMPONENT)
.then()
.isVisible(ActivityEmbeddingAppHelper.PLACEHOLDER_SECONDARY_COMPONENT)
@@ -142,21 +140,13 @@ class OpenActivityEmbeddingPlaceholderSplit(testSpec: FlickerTestParameter) :
/**
* Creates the test configurations.
*
- * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
- * screen orientation and navigation modes.
+ * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(
- supportedRotations = listOf(Surface.ROTATION_0, Surface.ROTATION_90),
- supportedNavigationModes =
- listOf(
- WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY,
- WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
- )
- )
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests()
}
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt
index b16bfe070374..d8917147bac4 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt
@@ -18,11 +18,11 @@ package com.android.server.wm.flicker.close
import android.platform.test.annotations.FlakyTest
import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.annotation.FlickerServiceCompatible
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -69,7 +69,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class CloseAppBackButtonTest(testSpec: FlickerTestParameter) : CloseAppTransition(testSpec) {
+class CloseAppBackButtonTest(flicker: FlickerTest) : CloseAppTransition(flicker) {
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit
get() = {
@@ -89,13 +89,13 @@ class CloseAppBackButtonTest(testSpec: FlickerTestParameter) : CloseAppTransitio
/**
* Creates the test configurations.
*
- * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
- * screen orientation and navigation modes.
+ * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests()
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests()
}
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt
index 78d0860f2636..cc8ef1df2527 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt
@@ -18,11 +18,11 @@ package com.android.server.wm.flicker.close
import android.platform.test.annotations.FlakyTest
import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.annotation.FlickerServiceCompatible
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -69,7 +69,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class CloseAppHomeButtonTest(testSpec: FlickerTestParameter) : CloseAppTransition(testSpec) {
+class CloseAppHomeButtonTest(flicker: FlickerTest) : CloseAppTransition(flicker) {
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit
get() = {
@@ -91,16 +91,11 @@ class CloseAppHomeButtonTest(testSpec: FlickerTestParameter) : CloseAppTransitio
override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
companion object {
- /**
- * Creates the test configurations.
- *
- * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
- * screen orientation and navigation modes.
- */
+ /** Creates the test configurations. */
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests()
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests()
}
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt
index 5bb227f36333..23503d2bc191 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt
@@ -18,8 +18,8 @@ package com.android.server.wm.flicker.close
import android.platform.test.annotations.Presubmit
import com.android.server.wm.flicker.BaseTest
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.helpers.SimpleAppHelper
import com.android.server.wm.flicker.helpers.StandardAppHelper
import com.android.server.wm.flicker.helpers.setRotation
@@ -28,15 +28,15 @@ import com.android.server.wm.traces.common.ComponentNameMatcher.Companion.LAUNCH
import org.junit.Test
/** Base test class for transitions that close an app back to the launcher screen */
-abstract class CloseAppTransition(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
+abstract class CloseAppTransition(flicker: FlickerTest) : BaseTest(flicker) {
protected open val testApp: StandardAppHelper = SimpleAppHelper(instrumentation)
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit = {
setup {
- tapl.setExpectedRotation(testSpec.startRotation)
+ tapl.setExpectedRotation(flicker.scenario.startRotation.value)
testApp.launchViaIntent(wmHelper)
- this.setRotation(testSpec.startRotation)
+ this.setRotation(flicker.scenario.startRotation)
}
teardown { testApp.exit(wmHelper) }
}
@@ -48,7 +48,7 @@ abstract class CloseAppTransition(testSpec: FlickerTestParameter) : BaseTest(tes
@Presubmit
@Test
open fun launcherReplacesAppWindowAsTopWindow() {
- testSpec.assertWm { this.isAppWindowOnTop(testApp).then().isAppWindowOnTop(LAUNCHER) }
+ flicker.assertWm { this.isAppWindowOnTop(testApp).then().isAppWindowOnTop(LAUNCHER) }
}
/**
@@ -58,17 +58,17 @@ abstract class CloseAppTransition(testSpec: FlickerTestParameter) : BaseTest(tes
@Presubmit
@Test
open fun launcherWindowBecomesVisible() {
- testSpec.assertWm { this.isAppWindowNotOnTop(LAUNCHER).then().isAppWindowOnTop(LAUNCHER) }
+ flicker.assertWm { this.isAppWindowNotOnTop(LAUNCHER).then().isAppWindowOnTop(LAUNCHER) }
}
/** Checks that [LAUNCHER] layer becomes visible when [testApp] becomes invisible */
@Presubmit
@Test
open fun launcherLayerReplacesApp() {
- testSpec.replacesLayer(
+ flicker.replacesLayer(
testApp,
LAUNCHER,
- ignoreEntriesWithRotationLayer = testSpec.isLandscapeOrSeascapeAtStart
+ ignoreEntriesWithRotationLayer = flicker.scenario.isLandscapeOrSeascapeAtStart
)
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt
index 48e1e64a05eb..368cc56d83b4 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt
@@ -17,8 +17,6 @@
package com.android.server.wm.flicker.helpers
import android.app.Instrumentation
-import android.support.test.launcherhelper.ILauncherStrategy
-import android.support.test.launcherhelper.LauncherStrategyFactory
import android.util.Log
import androidx.test.uiautomator.By
import androidx.test.uiautomator.Until
@@ -37,10 +35,8 @@ class ActivityEmbeddingAppHelper
constructor(
instr: Instrumentation,
launcherName: String = ActivityOptions.ActivityEmbedding.MainActivity.LABEL,
- component: ComponentNameMatcher = MAIN_ACTIVITY_COMPONENT,
- launcherStrategy: ILauncherStrategy =
- LauncherStrategyFactory.getInstance(instr).launcherStrategy
-) : StandardAppHelper(instr, launcherName, component, launcherStrategy) {
+ component: ComponentNameMatcher = MAIN_ACTIVITY_COMPONENT
+) : StandardAppHelper(instr, launcherName, component) {
/**
* Clicks the button to launch the placeholder primary activity, which should launch the
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/AssistantAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/AssistantAppHelper.kt
index efb92f208bde..18563ffbc0b7 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/AssistantAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/AssistantAppHelper.kt
@@ -25,45 +25,52 @@ import androidx.test.uiautomator.Until
import com.android.server.wm.flicker.testapp.ActivityOptions
import org.junit.Assert.assertNotNull
-class AssistantAppHelper @JvmOverloads constructor(
+class AssistantAppHelper
+@JvmOverloads
+constructor(
val instr: Instrumentation,
val component: ComponentName = ActivityOptions.ASSISTANT_SERVICE_COMPONENT_NAME,
) {
protected val uiDevice: UiDevice = UiDevice.getInstance(instr)
- protected val defaultAssistant: String? = Settings.Secure.getString(
- instr.targetContext.contentResolver,
- Settings.Secure.ASSISTANT)
- protected val defaultVoiceInteractionService: String? = Settings.Secure.getString(
- instr.targetContext.contentResolver,
- Settings.Secure.VOICE_INTERACTION_SERVICE)
+ protected val defaultAssistant: String? =
+ Settings.Secure.getString(instr.targetContext.contentResolver, Settings.Secure.ASSISTANT)
+ protected val defaultVoiceInteractionService: String? =
+ Settings.Secure.getString(
+ instr.targetContext.contentResolver,
+ Settings.Secure.VOICE_INTERACTION_SERVICE
+ )
fun setDefaultAssistant() {
Settings.Secure.putString(
instr.targetContext.contentResolver,
Settings.Secure.VOICE_INTERACTION_SERVICE,
- component.flattenToString())
+ component.flattenToString()
+ )
Settings.Secure.putString(
instr.targetContext.contentResolver,
Settings.Secure.ASSISTANT,
- component.flattenToString())
+ component.flattenToString()
+ )
}
fun resetDefaultAssistant() {
Settings.Secure.putString(
instr.targetContext.contentResolver,
Settings.Secure.VOICE_INTERACTION_SERVICE,
- defaultVoiceInteractionService)
+ defaultVoiceInteractionService
+ )
Settings.Secure.putString(
instr.targetContext.contentResolver,
Settings.Secure.ASSISTANT,
- defaultAssistant)
+ defaultAssistant
+ )
}
/**
* Open Assistance UI.
*
- * @param longpress open the UI by long pressing power button.
- * Otherwise open the UI through vioceinteraction shell command directly.
+ * @param longpress open the UI by long pressing power button. Otherwise open the UI through
+ * vioceinteraction shell command directly.
*/
@JvmOverloads
fun openUI(longpress: Boolean = false) {
@@ -72,9 +79,11 @@ class AssistantAppHelper @JvmOverloads constructor(
} else {
uiDevice.executeShellCommand("cmd voiceinteraction show")
}
- val ui = uiDevice.wait(
- Until.findObject(By.res(ActivityOptions.FLICKER_APP_PACKAGE, "vis_frame")),
- FIND_TIMEOUT)
+ val ui =
+ uiDevice.wait(
+ Until.findObject(By.res(ActivityOptions.FLICKER_APP_PACKAGE, "vis_frame")),
+ FIND_TIMEOUT
+ )
assertNotNull("Can't find Assistant UI after long pressing power button.", ui)
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FixedOrientationAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FixedOrientationAppHelper.kt
index 4340bd788046..05b50f06c08e 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FixedOrientationAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FixedOrientationAppHelper.kt
@@ -17,8 +17,6 @@
package com.android.server.wm.flicker.helpers
import android.app.Instrumentation
-import android.support.test.launcherhelper.ILauncherStrategy
-import android.support.test.launcherhelper.LauncherStrategyFactory
import com.android.server.wm.flicker.testapp.ActivityOptions
import com.android.server.wm.traces.common.ComponentNameMatcher
import com.android.server.wm.traces.parser.toFlickerComponent
@@ -29,7 +27,5 @@ constructor(
instr: Instrumentation,
launcherName: String = ActivityOptions.PortraitOnlyActivity.LABEL,
component: ComponentNameMatcher =
- ActivityOptions.PortraitOnlyActivity.COMPONENT.toFlickerComponent(),
- launcherStrategy: ILauncherStrategy =
- LauncherStrategyFactory.getInstance(instr).launcherStrategy
-) : StandardAppHelper(instr, launcherName, component, launcherStrategy)
+ ActivityOptions.PortraitOnlyActivity.COMPONENT.toFlickerComponent()
+) : StandardAppHelper(instr, launcherName, component)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt
index 73cb8627a068..2ae8e1d4932a 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt
@@ -18,15 +18,16 @@
package com.android.server.wm.flicker.helpers
-import com.android.server.wm.flicker.Flicker
+import com.android.server.wm.flicker.IFlickerTestData
import com.android.server.wm.flicker.rules.ChangeDisplayOrientationRule
+import com.android.server.wm.traces.common.service.PlatformConsts
/**
* Changes the device [rotation] and wait for the rotation animation to complete
*
* @param rotation New device rotation
*/
-fun Flicker.setRotation(rotation: Int) =
+fun IFlickerTestData.setRotation(rotation: PlatformConsts.Rotation) =
ChangeDisplayOrientationRule.setRotation(
rotation,
instrumentation,
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GameAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GameAppHelper.kt
index d45315e68e37..d583bbac20e0 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GameAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GameAppHelper.kt
@@ -17,8 +17,6 @@
package com.android.server.wm.flicker.helpers
import android.app.Instrumentation
-import android.support.test.launcherhelper.ILauncherStrategy
-import android.support.test.launcherhelper.LauncherStrategyFactory
import androidx.test.uiautomator.By
import androidx.test.uiautomator.Direction
import androidx.test.uiautomator.Until
@@ -32,10 +30,8 @@ class GameAppHelper
constructor(
instr: Instrumentation,
launcherName: String = ActivityOptions.Game.LABEL,
- component: ComponentNameMatcher = ActivityOptions.Game.COMPONENT.toFlickerComponent(),
- launcherStrategy: ILauncherStrategy =
- LauncherStrategyFactory.getInstance(instr).launcherStrategy,
-) : StandardAppHelper(instr, launcherName, component, launcherStrategy) {
+ component: ComponentNameMatcher = ActivityOptions.Game.COMPONENT.toFlickerComponent()
+) : StandardAppHelper(instr, launcherName, component) {
/**
* Swipes down in the mock game app.
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt
index ca5b2afd0917..b7eea1b58485 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt
@@ -26,6 +26,7 @@ import com.android.server.wm.flicker.testapp.ActivityOptions
import com.android.server.wm.traces.common.ComponentNameMatcher
import com.android.server.wm.traces.common.Condition
import com.android.server.wm.traces.common.DeviceStateDump
+import com.android.server.wm.traces.common.service.PlatformConsts
import com.android.server.wm.traces.parser.toFlickerComponent
import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
import java.util.regex.Pattern
@@ -34,7 +35,7 @@ class ImeAppAutoFocusHelper
@JvmOverloads
constructor(
instr: Instrumentation,
- private val rotation: Int,
+ private val rotation: PlatformConsts.Rotation,
private val imePackageName: String = IME_PACKAGE,
launcherName: String = ActivityOptions.Ime.AutoFocusActivity.LABEL,
component: ComponentNameMatcher =
@@ -63,7 +64,7 @@ constructor(
} else {
getPackage()
}
- launcherStrategy.launch(appName, expectedPackage)
+ open(expectedPackage)
}
fun startDialogThemedActivity(wmHelper: WindowManagerStateHelper) {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt
index cefbf18d33c9..3bb7f4ed2510 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt
@@ -17,8 +17,6 @@
package com.android.server.wm.flicker.helpers
import android.app.Instrumentation
-import android.support.test.launcherhelper.ILauncherStrategy
-import android.support.test.launcherhelper.LauncherStrategyFactory
import androidx.test.uiautomator.By
import androidx.test.uiautomator.Until
import com.android.server.wm.flicker.testapp.ActivityOptions
@@ -31,10 +29,8 @@ open class ImeAppHelper
constructor(
instr: Instrumentation,
launcherName: String = ActivityOptions.Ime.Default.LABEL,
- component: ComponentNameMatcher = ActivityOptions.Ime.Default.COMPONENT.toFlickerComponent(),
- launcherStrategy: ILauncherStrategy =
- LauncherStrategyFactory.getInstance(instr).launcherStrategy
-) : StandardAppHelper(instr, launcherName, component, launcherStrategy) {
+ component: ComponentNameMatcher = ActivityOptions.Ime.Default.COMPONENT.toFlickerComponent()
+) : StandardAppHelper(instr, launcherName, component) {
/**
* Opens the IME and wait for it to be displayed
*
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeStateInitializeHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeStateInitializeHelper.kt
index 1502ad5cde58..69d6a47d3dc1 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeStateInitializeHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeStateInitializeHelper.kt
@@ -17,8 +17,6 @@
package com.android.server.wm.flicker.helpers
import android.app.Instrumentation
-import android.support.test.launcherhelper.ILauncherStrategy
-import android.support.test.launcherhelper.LauncherStrategyFactory
import com.android.server.wm.flicker.testapp.ActivityOptions
import com.android.server.wm.traces.common.ComponentNameMatcher
import com.android.server.wm.traces.parser.toFlickerComponent
@@ -29,7 +27,5 @@ constructor(
instr: Instrumentation,
launcherName: String = ActivityOptions.Ime.StateInitializeActivity.LABEL,
component: ComponentNameMatcher =
- ActivityOptions.Ime.StateInitializeActivity.COMPONENT.toFlickerComponent(),
- launcherStrategy: ILauncherStrategy =
- LauncherStrategyFactory.getInstance(instr).launcherStrategy
-) : StandardAppHelper(instr, launcherName, component, launcherStrategy)
+ ActivityOptions.Ime.StateInitializeActivity.COMPONENT.toFlickerComponent()
+) : StandardAppHelper(instr, launcherName, component)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/MailAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/MailAppHelper.kt
index f00904bd7570..d0935ef1ded9 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/MailAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/MailAppHelper.kt
@@ -17,8 +17,6 @@
package com.android.server.wm.flicker.helpers
import android.app.Instrumentation
-import android.support.test.launcherhelper.ILauncherStrategy
-import android.support.test.launcherhelper.LauncherStrategyFactory
import androidx.test.uiautomator.By
import androidx.test.uiautomator.Direction
import androidx.test.uiautomator.UiObject2
@@ -32,10 +30,8 @@ class MailAppHelper
constructor(
instr: Instrumentation,
launcherName: String = ActivityOptions.Mail.LABEL,
- component: ComponentNameMatcher = ActivityOptions.Mail.COMPONENT.toFlickerComponent(),
- launcherStrategy: ILauncherStrategy =
- LauncherStrategyFactory.getInstance(instr).launcherStrategy
-) : StandardAppHelper(instr, launcherName, component, launcherStrategy) {
+ component: ComponentNameMatcher = ActivityOptions.Mail.COMPONENT.toFlickerComponent()
+) : StandardAppHelper(instr, launcherName, component) {
fun openMail(rowIdx: Int) {
val rowSel =
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NewTasksAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NewTasksAppHelper.kt
index 34294a6b7304..8b3fa185f1e4 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NewTasksAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NewTasksAppHelper.kt
@@ -17,8 +17,6 @@
package com.android.server.wm.flicker.helpers
import android.app.Instrumentation
-import android.support.test.launcherhelper.ILauncherStrategy
-import android.support.test.launcherhelper.LauncherStrategyFactory
import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.Until
@@ -32,10 +30,8 @@ class NewTasksAppHelper
constructor(
instr: Instrumentation,
launcherName: String = ActivityOptions.LaunchNewTask.LABEL,
- component: ComponentNameMatcher = ActivityOptions.LaunchNewTask.COMPONENT.toFlickerComponent(),
- launcherStrategy: ILauncherStrategy =
- LauncherStrategyFactory.getInstance(instr).launcherStrategy
-) : StandardAppHelper(instr, launcherName, component, launcherStrategy) {
+ component: ComponentNameMatcher = ActivityOptions.LaunchNewTask.COMPONENT.toFlickerComponent()
+) : StandardAppHelper(instr, launcherName, component) {
fun openNewTask(device: UiDevice, wmHelper: WindowManagerStateHelper) {
val button =
device.wait(Until.findObject(By.res(getPackage(), "launch_new_task")), FIND_TIMEOUT)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NonResizeableAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NonResizeableAppHelper.kt
index bbb782d7ae50..992a1a1b8ef6 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NonResizeableAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NonResizeableAppHelper.kt
@@ -17,8 +17,6 @@
package com.android.server.wm.flicker.helpers
import android.app.Instrumentation
-import android.support.test.launcherhelper.ILauncherStrategy
-import android.support.test.launcherhelper.LauncherStrategyFactory
import com.android.server.wm.flicker.testapp.ActivityOptions
import com.android.server.wm.traces.common.ComponentNameMatcher
import com.android.server.wm.traces.parser.toFlickerComponent
@@ -29,7 +27,5 @@ constructor(
instr: Instrumentation,
launcherName: String = ActivityOptions.NonResizeableActivity.LABEL,
component: ComponentNameMatcher =
- ActivityOptions.NonResizeableActivity.COMPONENT.toFlickerComponent(),
- launcherStrategy: ILauncherStrategy =
- LauncherStrategyFactory.getInstance(instr).launcherStrategy
-) : StandardAppHelper(instr, launcherName, component, launcherStrategy)
+ ActivityOptions.NonResizeableActivity.COMPONENT.toFlickerComponent()
+) : StandardAppHelper(instr, launcherName, component)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NotificationAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NotificationAppHelper.kt
index a3e32e501ce2..c29c75236141 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NotificationAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NotificationAppHelper.kt
@@ -17,8 +17,6 @@
package com.android.server.wm.flicker.helpers
import android.app.Instrumentation
-import android.support.test.launcherhelper.ILauncherStrategy
-import android.support.test.launcherhelper.LauncherStrategyFactory
import androidx.test.uiautomator.By
import androidx.test.uiautomator.Until
import com.android.server.wm.flicker.testapp.ActivityOptions
@@ -31,10 +29,8 @@ class NotificationAppHelper
constructor(
instr: Instrumentation,
launcherName: String = ActivityOptions.Notification.LABEL,
- component: ComponentNameMatcher = ActivityOptions.Notification.COMPONENT.toFlickerComponent(),
- launcherStrategy: ILauncherStrategy =
- LauncherStrategyFactory.getInstance(instr).launcherStrategy
-) : StandardAppHelper(instr, launcherName, component, launcherStrategy) {
+ component: ComponentNameMatcher = ActivityOptions.Notification.COMPONENT.toFlickerComponent()
+) : StandardAppHelper(instr, launcherName, component) {
fun postNotification(wmHelper: WindowManagerStateHelper) {
val button =
uiDevice.wait(Until.findObject(By.res(getPackage(), "post_notification")), FIND_TIMEOUT)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
index 19ee09a81ec5..8fe6aac4f727 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
@@ -92,8 +92,12 @@ open class PipAppHelper(instrumentation: Instrumentation) :
// if the distance per step is less than 1, carry out the animation in two steps
gestureHelper.pinch(
- Tuple(initLeftX, yCoord), Tuple(initRightX, yCoord),
- Tuple(finalLeftX, yCoord), Tuple(finalRightX, yCoord), adjustedSteps)
+ Tuple(initLeftX, yCoord),
+ Tuple(initRightX, yCoord),
+ Tuple(finalLeftX, yCoord),
+ Tuple(finalRightX, yCoord),
+ adjustedSteps
+ )
waitForPipWindowToExpandFrom(wmHelper, Region.from(windowRect))
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SeamlessRotationAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SeamlessRotationAppHelper.kt
index c90435223469..c51754c1fd8a 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SeamlessRotationAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SeamlessRotationAppHelper.kt
@@ -17,8 +17,6 @@
package com.android.server.wm.flicker.helpers
import android.app.Instrumentation
-import android.support.test.launcherhelper.ILauncherStrategy
-import android.support.test.launcherhelper.LauncherStrategyFactory
import com.android.server.wm.flicker.testapp.ActivityOptions
import com.android.server.wm.traces.common.ComponentNameMatcher
import com.android.server.wm.traces.parser.toFlickerComponent
@@ -29,7 +27,5 @@ constructor(
instr: Instrumentation,
launcherName: String = ActivityOptions.SeamlessRotation.LABEL,
component: ComponentNameMatcher =
- ActivityOptions.SeamlessRotation.COMPONENT.toFlickerComponent(),
- launcherStrategy: ILauncherStrategy =
- LauncherStrategyFactory.getInstance(instr).launcherStrategy
-) : StandardAppHelper(instr, launcherName, component, launcherStrategy)
+ ActivityOptions.SeamlessRotation.COMPONENT.toFlickerComponent()
+) : StandardAppHelper(instr, launcherName, component)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ShowWhenLockedAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ShowWhenLockedAppHelper.kt
index de152cb5bbc9..9318f2076bff 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ShowWhenLockedAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ShowWhenLockedAppHelper.kt
@@ -17,8 +17,6 @@
package com.android.server.wm.flicker.helpers
import android.app.Instrumentation
-import android.support.test.launcherhelper.ILauncherStrategy
-import android.support.test.launcherhelper.LauncherStrategyFactory
import com.android.server.wm.flicker.testapp.ActivityOptions
import com.android.server.wm.traces.common.ComponentNameMatcher
import com.android.server.wm.traces.parser.toFlickerComponent
@@ -29,7 +27,5 @@ constructor(
instr: Instrumentation,
launcherName: String = ActivityOptions.ShowWhenLockedActivity.LABEL,
component: ComponentNameMatcher =
- ActivityOptions.ShowWhenLockedActivity.COMPONENT.toFlickerComponent(),
- launcherStrategy: ILauncherStrategy =
- LauncherStrategyFactory.getInstance(instr).launcherStrategy
-) : StandardAppHelper(instr, launcherName, component, launcherStrategy)
+ ActivityOptions.ShowWhenLockedActivity.COMPONENT.toFlickerComponent()
+) : StandardAppHelper(instr, launcherName, component)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SimpleAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SimpleAppHelper.kt
index e41599002a01..b46ff2c5307e 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SimpleAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SimpleAppHelper.kt
@@ -17,8 +17,6 @@
package com.android.server.wm.flicker.helpers
import android.app.Instrumentation
-import android.support.test.launcherhelper.ILauncherStrategy
-import android.support.test.launcherhelper.LauncherStrategyFactory
import com.android.server.wm.flicker.testapp.ActivityOptions
import com.android.server.wm.traces.common.ComponentNameMatcher
import com.android.server.wm.traces.parser.toFlickerComponent
@@ -28,7 +26,5 @@ class SimpleAppHelper
constructor(
instr: Instrumentation,
launcherName: String = ActivityOptions.SimpleActivity.LABEL,
- component: ComponentNameMatcher = ActivityOptions.SimpleActivity.COMPONENT.toFlickerComponent(),
- launcherStrategy: ILauncherStrategy =
- LauncherStrategyFactory.getInstance(instr).launcherStrategy
-) : StandardAppHelper(instr, launcherName, component, launcherStrategy)
+ component: ComponentNameMatcher = ActivityOptions.SimpleActivity.COMPONENT.toFlickerComponent()
+) : StandardAppHelper(instr, launcherName, component)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt
index 13301906187d..720d962a7e54 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt
@@ -17,8 +17,6 @@
package com.android.server.wm.flicker.helpers
import android.app.Instrumentation
-import android.support.test.launcherhelper.ILauncherStrategy
-import android.support.test.launcherhelper.LauncherStrategyFactory
import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.Until
@@ -33,10 +31,8 @@ constructor(
instr: Instrumentation,
launcherName: String = ActivityOptions.LaunchNewActivity.LABEL,
component: ComponentNameMatcher =
- ActivityOptions.LaunchNewActivity.COMPONENT.toFlickerComponent(),
- launcherStrategy: ILauncherStrategy =
- LauncherStrategyFactory.getInstance(instr).launcherStrategy
-) : StandardAppHelper(instr, launcherName, component, launcherStrategy) {
+ ActivityOptions.LaunchNewActivity.COMPONENT.toFlickerComponent()
+) : StandardAppHelper(instr, launcherName, component) {
private val secondActivityComponent =
ActivityOptions.SimpleActivity.COMPONENT.toFlickerComponent()
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt
index 1a495957bb7f..c735be0a4c01 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt
@@ -17,16 +17,15 @@
package com.android.server.wm.flicker.ime
import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import android.view.WindowManagerPolicyConstants
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.BaseTest
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.traces.common.ComponentNameMatcher
+import com.android.server.wm.traces.common.service.PlatformConsts
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -49,8 +48,8 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class CloseImeAutoOpenWindowToAppTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
- private val testApp = ImeAppAutoFocusHelper(instrumentation, testSpec.startRotation)
+class CloseImeAutoOpenWindowToAppTest(flicker: FlickerTest) : BaseTest(flicker) {
+ private val testApp = ImeAppAutoFocusHelper(instrumentation, flicker.scenario.startRotation)
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit = {
@@ -62,43 +61,37 @@ class CloseImeAutoOpenWindowToAppTest(testSpec: FlickerTestParameter) : BaseTest
@Presubmit
@Test
fun imeAppWindowIsAlwaysVisible() {
- testSpec.assertWm { this.isAppWindowOnTop(testApp) }
+ flicker.assertWm { this.isAppWindowOnTop(testApp) }
}
@Presubmit
@Test
fun imeLayerVisibleStart() {
- testSpec.assertLayersStart { this.isVisible(ComponentNameMatcher.IME) }
+ flicker.assertLayersStart { this.isVisible(ComponentNameMatcher.IME) }
}
@Presubmit
@Test
fun imeLayerInvisibleEnd() {
- testSpec.assertLayersEnd { this.isInvisible(ComponentNameMatcher.IME) }
+ flicker.assertLayersEnd { this.isInvisible(ComponentNameMatcher.IME) }
}
- @Presubmit @Test fun imeLayerBecomesInvisible() = testSpec.imeLayerBecomesInvisible()
+ @Presubmit @Test fun imeLayerBecomesInvisible() = flicker.imeLayerBecomesInvisible()
@Presubmit
@Test
fun imeAppLayerIsAlwaysVisible() {
- testSpec.assertLayers { this.isVisible(testApp) }
+ flicker.assertLayers { this.isVisible(testApp) }
}
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(
- // b/190352379 (IME doesn't show on app launch in 90 degrees)
- supportedRotations = listOf(Surface.ROTATION_0),
- supportedNavigationModes =
- listOf(
- WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY,
- WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
- )
- )
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ // b/190352379 (IME doesn't show on app launch in 90 degrees)
+ supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
+ )
}
}
}
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 463efe89b5e9..4024f569186e 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
@@ -17,16 +17,15 @@
package com.android.server.wm.flicker.ime
import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import android.view.WindowManagerPolicyConstants
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.BaseTest
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.traces.common.ComponentNameMatcher
+import com.android.server.wm.traces.common.service.PlatformConsts
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -49,8 +48,8 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class CloseImeAutoOpenWindowToHomeTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
- private val testApp = ImeAppAutoFocusHelper(instrumentation, testSpec.startRotation)
+class CloseImeAutoOpenWindowToHomeTest(flicker: FlickerTest) : BaseTest(flicker) {
+ private val testApp = ImeAppAutoFocusHelper(instrumentation, flicker.scenario.startRotation)
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit = {
@@ -68,43 +67,37 @@ class CloseImeAutoOpenWindowToHomeTest(testSpec: FlickerTestParameter) : BaseTes
@Presubmit
@Test
fun imeAppWindowBecomesInvisible() {
- testSpec.assertWm { this.isAppWindowOnTop(testApp).then().isAppWindowNotOnTop(testApp) }
+ flicker.assertWm { this.isAppWindowOnTop(testApp).then().isAppWindowNotOnTop(testApp) }
}
@Presubmit
@Test
fun imeLayerVisibleStart() {
- testSpec.assertLayersStart { this.isVisible(ComponentNameMatcher.IME) }
+ flicker.assertLayersStart { this.isVisible(ComponentNameMatcher.IME) }
}
@Presubmit
@Test
fun imeLayerInvisibleEnd() {
- testSpec.assertLayersEnd { this.isInvisible(ComponentNameMatcher.IME) }
+ flicker.assertLayersEnd { this.isInvisible(ComponentNameMatcher.IME) }
}
- @Presubmit @Test fun imeLayerBecomesInvisible() = testSpec.imeLayerBecomesInvisible()
+ @Presubmit @Test fun imeLayerBecomesInvisible() = flicker.imeLayerBecomesInvisible()
@Presubmit
@Test
fun imeAppLayerBecomesInvisible() {
- testSpec.assertLayers { this.isVisible(testApp).then().isInvisible(testApp) }
+ flicker.assertLayers { this.isVisible(testApp).then().isInvisible(testApp) }
}
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(
- // b/190352379 (IME doesn't show on app launch in 90 degrees)
- supportedRotations = listOf(Surface.ROTATION_0),
- supportedNavigationModes =
- listOf(
- WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY,
- WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
- )
- )
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ // b/190352379 (IME doesn't show on app launch in 90 degrees)
+ supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
+ )
}
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeEditorPopupDialogTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeEditorPopupDialogTest.kt
index 91d9a1f17d05..c72405c7854d 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeEditorPopupDialogTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeEditorPopupDialogTest.kt
@@ -17,17 +17,16 @@
package com.android.server.wm.flicker.ime
import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import android.view.WindowManagerPolicyConstants
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.BaseTest
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.helpers.ImeEditorPopupDialogAppHelper
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.traces.region.RegionSubject
import com.android.server.wm.traces.common.ComponentNameMatcher
+import com.android.server.wm.traces.common.service.PlatformConsts
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -38,7 +37,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class CloseImeEditorPopupDialogTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
+class CloseImeEditorPopupDialogTest(flicker: FlickerTest) : BaseTest(flicker) {
private val imeTestApp = ImeEditorPopupDialogAppHelper(instrumentation)
/** {@inheritDoc} */
@@ -59,14 +58,12 @@ class CloseImeEditorPopupDialogTest(testSpec: FlickerTestParameter) : BaseTest(t
}
}
- @Presubmit
- @Test
- fun imeWindowBecameInvisible() = testSpec.imeWindowBecomesInvisible()
+ @Presubmit @Test fun imeWindowBecameInvisible() = flicker.imeWindowBecomesInvisible()
@Presubmit
@Test
fun imeLayerAndImeSnapshotVisibleOnScreen() {
- testSpec.assertLayers {
+ flicker.assertLayers {
this.isVisible(ComponentNameMatcher.IME)
.then()
.isVisible(ComponentNameMatcher.IME_SNAPSHOT)
@@ -79,7 +76,7 @@ class CloseImeEditorPopupDialogTest(testSpec: FlickerTestParameter) : BaseTest(t
@Presubmit
@Test
fun imeSnapshotAssociatedOnAppVisibleRegion() {
- testSpec.assertLayers {
+ flicker.assertLayers {
this.invoke("imeSnapshotAssociatedOnAppVisibleRegion") {
val imeSnapshotLayers =
it.subjects.filter { subject ->
@@ -106,16 +103,10 @@ class CloseImeEditorPopupDialogTest(testSpec: FlickerTestParameter) : BaseTest(t
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(
- supportedNavigationModes =
- listOf(
- WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY,
- WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
- ),
- supportedRotations = listOf(Surface.ROTATION_0)
- )
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
+ )
}
}
}
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 ef42766bb04e..afc5f650cb53 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
@@ -21,11 +21,11 @@ import android.platform.test.annotations.IwTest
import android.platform.test.annotations.Presubmit
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.BaseTest
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.helpers.ImeAppHelper
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.navBarLayerPositionAtStartAndEnd
import com.android.server.wm.traces.common.ComponentNameMatcher
import org.junit.Assume
@@ -43,7 +43,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class CloseImeWindowToAppTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
+class CloseImeWindowToAppTest(flicker: FlickerTest) : BaseTest(flicker) {
private val testApp = ImeAppHelper(instrumentation)
/** {@inheritDoc} */
@@ -60,7 +60,7 @@ class CloseImeWindowToAppTest(testSpec: FlickerTestParameter) : BaseTest(testSpe
@Presubmit
@Test
override fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
- testSpec.assertWm {
+ flicker.assertWm {
this.visibleWindowsShownMoreThanOneConsecutiveEntry(
listOf(
ComponentNameMatcher.IME,
@@ -75,31 +75,31 @@ class CloseImeWindowToAppTest(testSpec: FlickerTestParameter) : BaseTest(testSpe
@Presubmit
@Test
override fun navBarLayerPositionAtStartAndEnd() {
- Assume.assumeFalse(testSpec.isTablet)
- Assume.assumeFalse(testSpec.isLandscapeOrSeascapeAtStart)
- testSpec.navBarLayerPositionAtStartAndEnd()
+ Assume.assumeFalse(flicker.scenario.isTablet)
+ Assume.assumeFalse(flicker.scenario.isLandscapeOrSeascapeAtStart)
+ flicker.navBarLayerPositionAtStartAndEnd()
}
@FlakyTest
@Test
fun navBarLayerPositionAtStartAndEndLandscapeOrSeascapeAtStart() {
- Assume.assumeFalse(testSpec.isTablet)
- Assume.assumeTrue(testSpec.isLandscapeOrSeascapeAtStart)
- testSpec.navBarLayerPositionAtStartAndEnd()
+ Assume.assumeFalse(flicker.scenario.isTablet)
+ Assume.assumeTrue(flicker.scenario.isLandscapeOrSeascapeAtStart)
+ flicker.navBarLayerPositionAtStartAndEnd()
}
- @Presubmit @Test fun imeLayerBecomesInvisible() = testSpec.imeLayerBecomesInvisible()
+ @Presubmit @Test fun imeLayerBecomesInvisible() = flicker.imeLayerBecomesInvisible()
@Presubmit
@Test
fun imeAppLayerIsAlwaysVisible() {
- testSpec.assertLayers { this.isVisible(testApp) }
+ flicker.assertLayers { this.isVisible(testApp) }
}
@Presubmit
@Test
fun imeAppWindowIsAlwaysVisible() {
- testSpec.assertWm { this.isAppWindowOnTop(testApp) }
+ flicker.assertWm { this.isAppWindowOnTop(testApp) }
}
@Test
@@ -115,8 +115,8 @@ class CloseImeWindowToAppTest(testSpec: FlickerTestParameter) : BaseTest(testSpe
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests()
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests()
}
}
}
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 c92fce33188e..aedf965137b0 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
@@ -18,16 +18,15 @@ package com.android.server.wm.flicker.ime
import android.platform.test.annotations.IwTest
import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import android.view.WindowManagerPolicyConstants
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.BaseTest
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.helpers.ImeAppHelper
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.traces.common.ComponentNameMatcher
+import com.android.server.wm.traces.common.service.PlatformConsts
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -42,7 +41,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class CloseImeWindowToHomeTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
+class CloseImeWindowToHomeTest(flicker: FlickerTest) : BaseTest(flicker) {
private val testApp = ImeAppHelper(instrumentation)
/** {@inheritDoc} */
@@ -63,7 +62,7 @@ class CloseImeWindowToHomeTest(testSpec: FlickerTestParameter) : BaseTest(testSp
@Presubmit
@Test
override fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
- testSpec.assertWm {
+ flicker.assertWm {
this.visibleWindowsShownMoreThanOneConsecutiveEntry(
listOf(
ComponentNameMatcher.IME,
@@ -78,27 +77,27 @@ class CloseImeWindowToHomeTest(testSpec: FlickerTestParameter) : BaseTest(testSp
@Presubmit
@Test
override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
- testSpec.assertLayers {
+ flicker.assertLayers {
this.visibleLayersShownMoreThanOneConsecutiveEntry(
listOf(ComponentNameMatcher.IME, ComponentNameMatcher.SPLASH_SCREEN)
)
}
}
- @Presubmit @Test fun imeLayerBecomesInvisible() = testSpec.imeLayerBecomesInvisible()
+ @Presubmit @Test fun imeLayerBecomesInvisible() = flicker.imeLayerBecomesInvisible()
- @Presubmit @Test fun imeWindowBecomesInvisible() = testSpec.imeWindowBecomesInvisible()
+ @Presubmit @Test fun imeWindowBecomesInvisible() = flicker.imeWindowBecomesInvisible()
@Presubmit
@Test
fun imeAppWindowBecomesInvisible() {
- testSpec.assertWm { this.isAppWindowVisible(testApp).then().isAppWindowInvisible(testApp) }
+ flicker.assertWm { this.isAppWindowVisible(testApp).then().isAppWindowInvisible(testApp) }
}
@Presubmit
@Test
fun imeAppLayerBecomesInvisible() {
- testSpec.assertLayers { this.isVisible(testApp).then().isInvisible(testApp) }
+ flicker.assertLayers { this.isVisible(testApp).then().isInvisible(testApp) }
}
@Test
@@ -115,16 +114,10 @@ class CloseImeWindowToHomeTest(testSpec: FlickerTestParameter) : BaseTest(testSp
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(
- supportedRotations = listOf(Surface.ROTATION_0),
- supportedNavigationModes =
- listOf(
- WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY,
- WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
- )
- )
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
+ )
}
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CommonAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CommonAssertions.kt
index e0c5edcec762..3edc15f7f6bf 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CommonAssertions.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CommonAssertions.kt
@@ -18,22 +18,22 @@
package com.android.server.wm.flicker.ime
-import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.traces.common.ComponentNameMatcher
-fun FlickerTestParameter.imeLayerBecomesVisible() {
+fun FlickerTest.imeLayerBecomesVisible() {
assertLayers {
this.isInvisible(ComponentNameMatcher.IME).then().isVisible(ComponentNameMatcher.IME)
}
}
-fun FlickerTestParameter.imeLayerBecomesInvisible() {
+fun FlickerTest.imeLayerBecomesInvisible() {
assertLayers {
this.isVisible(ComponentNameMatcher.IME).then().isInvisible(ComponentNameMatcher.IME)
}
}
-fun FlickerTestParameter.imeWindowIsAlwaysVisible(rotatesScreen: Boolean = false) {
+fun FlickerTest.imeWindowIsAlwaysVisible(rotatesScreen: Boolean = false) {
if (rotatesScreen) {
assertWm {
this.isNonAppWindowVisible(ComponentNameMatcher.IME)
@@ -47,7 +47,7 @@ fun FlickerTestParameter.imeWindowIsAlwaysVisible(rotatesScreen: Boolean = false
}
}
-fun FlickerTestParameter.imeWindowBecomesVisible() {
+fun FlickerTest.imeWindowBecomesVisible() {
assertWm {
this.isNonAppWindowInvisible(ComponentNameMatcher.IME)
.then()
@@ -55,7 +55,7 @@ fun FlickerTestParameter.imeWindowBecomesVisible() {
}
}
-fun FlickerTestParameter.imeWindowBecomesInvisible() {
+fun FlickerTest.imeWindowBecomesInvisible() {
assertWm {
this.isNonAppWindowVisible(ComponentNameMatcher.IME)
.then()
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeAndDialogThemeAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeAndDialogThemeAppTest.kt
index 073da4f092dc..da3c62daccd9 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeAndDialogThemeAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeAndDialogThemeAppTest.kt
@@ -18,19 +18,18 @@ package com.android.server.wm.flicker.ime
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
-import android.view.Surface
import android.view.WindowInsets.Type.ime
import android.view.WindowInsets.Type.navigationBars
import android.view.WindowInsets.Type.statusBars
-import android.view.WindowManagerPolicyConstants
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.BaseTest
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.traces.common.ComponentNameMatcher
+import com.android.server.wm.traces.common.service.PlatformConsts
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.FixMethodOrder
@@ -47,8 +46,8 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class LaunchAppShowImeAndDialogThemeAppTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
- private val testApp = ImeAppAutoFocusHelper(instrumentation, testSpec.startRotation)
+class LaunchAppShowImeAndDialogThemeAppTest(flicker: FlickerTest) : BaseTest(flicker) {
+ private val testApp = ImeAppAutoFocusHelper(instrumentation, flicker.scenario.startRotation)
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit = {
@@ -75,42 +74,36 @@ class LaunchAppShowImeAndDialogThemeAppTest(testSpec: FlickerTestParameter) : Ba
@Test
override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
- /** Checks that [ComponentMatcher.IME] layer becomes visible during the transition */
- @Presubmit @Test fun imeWindowIsAlwaysVisible() = testSpec.imeWindowIsAlwaysVisible()
+ /** Checks that [ComponentNameMatcher.IME] layer becomes visible during the transition */
+ @Presubmit @Test fun imeWindowIsAlwaysVisible() = flicker.imeWindowIsAlwaysVisible()
- /** Checks that [ComponentMatcher.IME] layer is visible at the end of the transition */
+ /** Checks that [ComponentNameMatcher.IME] layer is visible at the end of the transition */
@Presubmit
@Test
fun imeLayerExistsEnd() {
- testSpec.assertLayersEnd { this.isVisible(ComponentNameMatcher.IME) }
+ flicker.assertLayersEnd { this.isVisible(ComponentNameMatcher.IME) }
}
- /** Checks that [ComponentMatcher.IME_SNAPSHOT] layer is invisible always. */
+ /** Checks that [ComponentNameMatcher.IME_SNAPSHOT] layer is invisible always. */
@Presubmit
@Test
fun imeSnapshotNotVisible() {
- testSpec.assertLayers { this.isInvisible(ComponentNameMatcher.IME_SNAPSHOT) }
+ flicker.assertLayers { this.isInvisible(ComponentNameMatcher.IME_SNAPSHOT) }
}
companion object {
/**
* Creates the test configurations.
*
- * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
- * screen orientation and navigation modes.
+ * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(
- supportedRotations = listOf(Surface.ROTATION_0),
- supportedNavigationModes =
- listOf(
- WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY,
- WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
- )
- )
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
+ )
}
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt
index a93f1766b05f..48919015ac85 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt
@@ -17,18 +17,17 @@
package com.android.server.wm.flicker.ime
import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import android.view.WindowManagerPolicyConstants
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.BaseTest
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
import com.android.server.wm.flicker.helpers.ImeStateInitializeHelper
import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.traces.common.ComponentNameMatcher
+import com.android.server.wm.traces.common.service.PlatformConsts
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -73,15 +72,15 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class LaunchAppShowImeOnStartTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
- private val testApp = ImeAppAutoFocusHelper(instrumentation, testSpec.startRotation)
+class LaunchAppShowImeOnStartTest(flicker: FlickerTest) : BaseTest(flicker) {
+ private val testApp = ImeAppAutoFocusHelper(instrumentation, flicker.scenario.startRotation)
private val initializeApp = ImeStateInitializeHelper(instrumentation)
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit = {
setup {
initializeApp.launchViaIntent(wmHelper)
- this.setRotation(testSpec.startRotation)
+ this.setRotation(flicker.scenario.startRotation)
}
teardown {
initializeApp.exit(wmHelper)
@@ -93,45 +92,39 @@ class LaunchAppShowImeOnStartTest(testSpec: FlickerTestParameter) : BaseTest(tes
}
}
- /** Checks that [ComponentMatcher.IME] window becomes visible during the transition */
- @Presubmit @Test fun imeWindowBecomesVisible() = testSpec.imeWindowBecomesVisible()
+ /** Checks that [ComponentNameMatcher.IME] window becomes visible during the transition */
+ @Presubmit @Test fun imeWindowBecomesVisible() = flicker.imeWindowBecomesVisible()
- /** Checks that [ComponentMatcher.IME] layer becomes visible during the transition */
- @Presubmit @Test fun imeLayerBecomesVisible() = testSpec.imeLayerBecomesVisible()
+ /** Checks that [ComponentNameMatcher.IME] layer becomes visible during the transition */
+ @Presubmit @Test fun imeLayerBecomesVisible() = flicker.imeLayerBecomesVisible()
- /** Checks that [ComponentMatcher.IME] layer is invisible at the start of the transition */
+ /** Checks that [ComponentNameMatcher.IME] layer is invisible at the start of the transition */
@Presubmit
@Test
fun imeLayerNotExistsStart() {
- testSpec.assertLayersStart { this.isInvisible(ComponentNameMatcher.IME) }
+ flicker.assertLayersStart { this.isInvisible(ComponentNameMatcher.IME) }
}
- /** Checks that [ComponentMatcher.IME] layer is visible at the end of the transition */
+ /** Checks that [ComponentNameMatcher.IME] layer is visible at the end of the transition */
@Presubmit
@Test
fun imeLayerExistsEnd() {
- testSpec.assertLayersEnd { this.isVisible(ComponentNameMatcher.IME) }
+ flicker.assertLayersEnd { this.isVisible(ComponentNameMatcher.IME) }
}
companion object {
/**
* Creates the test configurations.
*
- * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
- * screen orientation and navigation modes.
+ * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(
- supportedRotations = listOf(Surface.ROTATION_0),
- supportedNavigationModes =
- listOf(
- WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY,
- WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
- )
- )
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
+ )
}
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowAndCloseTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowAndCloseTest.kt
index 7d7953b0c4dc..33e957482cb1 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowAndCloseTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowAndCloseTest.kt
@@ -19,17 +19,16 @@ package com.android.server.wm.flicker.ime
import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.IwTest
import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import android.view.WindowManagerPolicyConstants
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.BaseTest
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.helpers.ImeAppHelper
import com.android.server.wm.flicker.helpers.SimpleAppHelper
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import com.android.server.wm.traces.common.service.PlatformConsts
import org.junit.Assume
import org.junit.FixMethodOrder
import org.junit.Test
@@ -47,7 +46,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class OpenImeWindowAndCloseTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
+class OpenImeWindowAndCloseTest(flicker: FlickerTest) : BaseTest(flicker) {
private val simpleApp = SimpleAppHelper(instrumentation)
private val testApp = ImeAppHelper(instrumentation)
@@ -62,9 +61,9 @@ class OpenImeWindowAndCloseTest(testSpec: FlickerTestParameter) : BaseTest(testS
teardown { simpleApp.exit(wmHelper) }
}
- @Presubmit @Test fun imeWindowBecomesInvisible() = testSpec.imeWindowBecomesInvisible()
+ @Presubmit @Test fun imeWindowBecomesInvisible() = flicker.imeWindowBecomesInvisible()
- @Presubmit @Test fun imeLayerBecomesInvisible() = testSpec.imeLayerBecomesInvisible()
+ @Presubmit @Test fun imeLayerBecomesInvisible() = flicker.imeLayerBecomesInvisible()
@Presubmit
@Test
@@ -91,16 +90,10 @@ class OpenImeWindowAndCloseTest(testSpec: FlickerTestParameter) : BaseTest(testS
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(
- supportedRotations = listOf(Surface.ROTATION_0),
- supportedNavigationModes =
- listOf(
- WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY,
- WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
- )
- )
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
+ )
}
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowFromFixedOrientationAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowFromFixedOrientationAppTest.kt
index 3e18d5957eb7..197564a6039a 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowFromFixedOrientationAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowFromFixedOrientationAppTest.kt
@@ -19,17 +19,16 @@ package com.android.server.wm.flicker.ime
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import android.platform.test.annotations.RequiresDevice
-import android.view.Surface
-import android.view.WindowManagerPolicyConstants
import com.android.server.wm.flicker.BaseTest
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.snapshotStartingWindowLayerCoversExactlyOnApp
+import com.android.server.wm.traces.common.service.PlatformConsts
import org.junit.Assume
import org.junit.FixMethodOrder
import org.junit.Test
@@ -46,9 +45,8 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class OpenImeWindowFromFixedOrientationAppTest(testSpec: FlickerTestParameter) :
- BaseTest(testSpec) {
- private val imeTestApp = ImeAppAutoFocusHelper(instrumentation, testSpec.startRotation)
+class OpenImeWindowFromFixedOrientationAppTest(flicker: FlickerTest) : BaseTest(flicker) {
+ private val imeTestApp = ImeAppAutoFocusHelper(instrumentation, flicker.scenario.startRotation)
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit = {
@@ -63,61 +61,58 @@ class OpenImeWindowFromFixedOrientationAppTest(testSpec: FlickerTestParameter) :
wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
}
transitions {
- // Bring the exist IME activity to the front in landscape mode device rotation.
- setRotation(Surface.ROTATION_90)
+ // Bring the existing IME activity to the front in landscape mode device rotation.
+ setRotation(PlatformConsts.Rotation.ROTATION_90)
imeTestApp.launchViaIntent(wmHelper)
}
teardown { imeTestApp.exit(wmHelper) }
}
/** {@inheritDoc} */
- @Presubmit
+ @Postsubmit
@Test
override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
+ @Postsubmit
+ @Test
+ override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
+
/** {@inheritDoc} */
@Postsubmit
@Test
override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
- @Presubmit
- @Test
- fun imeWindowBecomesVisible() = testSpec.imeWindowBecomesVisible()
+ @Presubmit @Test fun imeWindowBecomesVisible() = flicker.imeWindowBecomesVisible()
- @Presubmit
- @Test
- fun imeLayerBecomesVisible() = testSpec.imeLayerBecomesVisible()
+ @Presubmit @Test fun imeLayerBecomesVisible() = flicker.imeLayerBecomesVisible()
@Postsubmit
@Test
fun snapshotStartingWindowLayerCoversExactlyOnApp() {
Assume.assumeFalse(isShellTransitionsEnabled)
- testSpec.snapshotStartingWindowLayerCoversExactlyOnApp(imeTestApp)
+ flicker.snapshotStartingWindowLayerCoversExactlyOnApp(imeTestApp)
}
@Presubmit
@Test
fun snapshotStartingWindowLayerCoversExactlyOnApp_ShellTransit() {
Assume.assumeTrue(isShellTransitionsEnabled)
- testSpec.snapshotStartingWindowLayerCoversExactlyOnApp(imeTestApp)
+ flicker.snapshotStartingWindowLayerCoversExactlyOnApp(imeTestApp)
}
companion object {
/**
* Creates the test configurations.
*
- * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
- * screen orientation and navigation modes.
+ * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(
- supportedRotations = listOf(Surface.ROTATION_90),
- supportedNavigationModes =
- listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY)
- )
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_90)
+ )
}
}
}
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 9919d873525d..c097511bd1c1 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
@@ -18,15 +18,14 @@ package com.android.server.wm.flicker.ime
import android.platform.test.annotations.IwTest
import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import android.view.WindowManagerPolicyConstants
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.BaseTest
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.helpers.ImeAppHelper
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import com.android.server.wm.traces.common.service.PlatformConsts
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -38,7 +37,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class OpenImeWindowTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
+class OpenImeWindowTest(flicker: FlickerTest) : BaseTest(flicker) {
private val testApp = ImeAppHelper(instrumentation)
/** {@inheritDoc} */
@@ -60,35 +59,29 @@ class OpenImeWindowTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
layerAlwaysVisible()
}
- @Presubmit @Test fun imeWindowBecomesVisible() = testSpec.imeWindowBecomesVisible()
+ @Presubmit @Test fun imeWindowBecomesVisible() = flicker.imeWindowBecomesVisible()
@Presubmit
@Test
fun appWindowAlwaysVisibleOnTop() {
- testSpec.assertWm { this.isAppWindowOnTop(testApp) }
+ flicker.assertWm { this.isAppWindowOnTop(testApp) }
}
- @Presubmit @Test fun imeLayerBecomesVisible() = testSpec.imeLayerBecomesVisible()
+ @Presubmit @Test fun imeLayerBecomesVisible() = flicker.imeLayerBecomesVisible()
@Presubmit
@Test
fun layerAlwaysVisible() {
- testSpec.assertLayers { this.isVisible(testApp) }
+ flicker.assertLayers { this.isVisible(testApp) }
}
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(
- supportedRotations = listOf(Surface.ROTATION_0),
- supportedNavigationModes =
- listOf(
- WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY,
- WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
- )
- )
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
+ )
}
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTest.kt
index 0a7701e54b31..209eb0c22b62 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTest.kt
@@ -18,15 +18,13 @@ package com.android.server.wm.flicker.ime
import android.platform.test.annotations.Presubmit
import android.platform.test.annotations.RequiresDevice
-import android.view.Surface
-import android.view.WindowManagerPolicyConstants
import com.android.server.wm.flicker.BaseTest
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.navBarLayerIsVisibleAtStartAndEnd
import com.android.server.wm.flicker.statusBarLayerIsVisibleAtStartAndEnd
import com.android.server.wm.traces.common.ComponentNameMatcher
@@ -48,8 +46,8 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class OpenImeWindowToOverViewTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
- private val imeTestApp = ImeAppAutoFocusHelper(instrumentation, testSpec.startRotation)
+class OpenImeWindowToOverViewTest(flicker: FlickerTest) : BaseTest(flicker) {
+ private val imeTestApp = ImeAppAutoFocusHelper(instrumentation, flicker.scenario.startRotation)
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit = {
@@ -82,7 +80,7 @@ class OpenImeWindowToOverViewTest(testSpec: FlickerTestParameter) : BaseTest(tes
*/
private fun waitNavStatusBarVisibility(stateSync: WindowManagerStateHelper.StateSyncBuilder) {
when {
- testSpec.isLandscapeOrSeascapeAtStart && !testSpec.isTablet ->
+ flicker.scenario.isLandscapeOrSeascapeAtStart && !flicker.scenario.isTablet ->
stateSync.add(WindowManagerConditionsFactory.isStatusBarVisible().negate())
else -> stateSync.withNavOrTaskBarVisible().withStatusBarVisible()
}
@@ -91,25 +89,25 @@ class OpenImeWindowToOverViewTest(testSpec: FlickerTestParameter) : BaseTest(tes
@Presubmit
@Test
fun imeWindowIsAlwaysVisible() {
- testSpec.imeWindowIsAlwaysVisible()
+ flicker.imeWindowIsAlwaysVisible()
}
@Presubmit
@Test
fun navBarLayerIsVisibleAtStartAndEnd3Button() {
- Assume.assumeFalse(testSpec.isTablet)
- Assume.assumeFalse(testSpec.isGesturalNavigation)
- testSpec.navBarLayerIsVisibleAtStartAndEnd()
+ Assume.assumeFalse(flicker.scenario.isTablet)
+ Assume.assumeFalse(flicker.scenario.isGesturalNavigation)
+ flicker.navBarLayerIsVisibleAtStartAndEnd()
}
/** Bars are expected to be hidden while entering overview in landscape (b/227189877) */
@Presubmit
@Test
fun navBarLayerIsVisibleAtStartAndEndGestural() {
- Assume.assumeFalse(testSpec.isTablet)
- Assume.assumeTrue(testSpec.isGesturalNavigation)
+ Assume.assumeFalse(flicker.scenario.isTablet)
+ Assume.assumeTrue(flicker.scenario.isGesturalNavigation)
Assume.assumeFalse(isShellTransitionsEnabled)
- testSpec.navBarLayerIsVisibleAtStartAndEnd()
+ flicker.navBarLayerIsVisibleAtStartAndEnd()
}
/**
@@ -119,12 +117,12 @@ class OpenImeWindowToOverViewTest(testSpec: FlickerTestParameter) : BaseTest(tes
@Presubmit
@Test
fun navBarLayerIsInvisibleInLandscapeGestural() {
- Assume.assumeFalse(testSpec.isTablet)
- Assume.assumeTrue(testSpec.isLandscapeOrSeascapeAtStart)
- Assume.assumeTrue(testSpec.isGesturalNavigation)
+ Assume.assumeFalse(flicker.scenario.isTablet)
+ Assume.assumeTrue(flicker.scenario.isLandscapeOrSeascapeAtStart)
+ Assume.assumeTrue(flicker.scenario.isGesturalNavigation)
Assume.assumeTrue(isShellTransitionsEnabled)
- testSpec.assertLayersStart { this.isVisible(ComponentNameMatcher.NAV_BAR) }
- testSpec.assertLayersEnd { this.isInvisible(ComponentNameMatcher.NAV_BAR) }
+ flicker.assertLayersStart { this.isVisible(ComponentNameMatcher.NAV_BAR) }
+ flicker.assertLayersEnd { this.isInvisible(ComponentNameMatcher.NAV_BAR) }
}
/**
@@ -134,11 +132,11 @@ class OpenImeWindowToOverViewTest(testSpec: FlickerTestParameter) : BaseTest(tes
@Presubmit
@Test
fun statusBarLayerIsInvisibleInLandscapePhone() {
- Assume.assumeTrue(testSpec.isLandscapeOrSeascapeAtStart)
- Assume.assumeTrue(testSpec.isGesturalNavigation)
- Assume.assumeFalse(testSpec.isTablet)
- testSpec.assertLayersStart { this.isVisible(ComponentNameMatcher.STATUS_BAR) }
- testSpec.assertLayersEnd { this.isInvisible(ComponentNameMatcher.STATUS_BAR) }
+ Assume.assumeTrue(flicker.scenario.isLandscapeOrSeascapeAtStart)
+ Assume.assumeTrue(flicker.scenario.isGesturalNavigation)
+ Assume.assumeFalse(flicker.scenario.isTablet)
+ flicker.assertLayersStart { this.isVisible(ComponentNameMatcher.STATUS_BAR) }
+ flicker.assertLayersEnd { this.isInvisible(ComponentNameMatcher.STATUS_BAR) }
}
/**
@@ -148,35 +146,31 @@ class OpenImeWindowToOverViewTest(testSpec: FlickerTestParameter) : BaseTest(tes
@Presubmit
@Test
fun statusBarLayerIsInvisibleInLandscapeTablet() {
- Assume.assumeTrue(testSpec.isLandscapeOrSeascapeAtStart)
- Assume.assumeTrue(testSpec.isGesturalNavigation)
- Assume.assumeTrue(testSpec.isTablet)
- testSpec.statusBarLayerIsVisibleAtStartAndEnd()
+ Assume.assumeTrue(flicker.scenario.isLandscapeOrSeascapeAtStart)
+ Assume.assumeTrue(flicker.scenario.isGesturalNavigation)
+ Assume.assumeTrue(flicker.scenario.isTablet)
+ flicker.statusBarLayerIsVisibleAtStartAndEnd()
}
/** {@inheritDoc} */
@Test
@Ignore("Visibility changes depending on orientation and navigation mode")
- override fun navBarLayerIsVisibleAtStartAndEnd() {
- }
+ override fun navBarLayerIsVisibleAtStartAndEnd() {}
/** {@inheritDoc} */
@Test
@Ignore("Visibility changes depending on orientation and navigation mode")
- override fun navBarLayerPositionAtStartAndEnd() {
- }
+ override fun navBarLayerPositionAtStartAndEnd() {}
/** {@inheritDoc} */
@Test
@Ignore("Visibility changes depending on orientation and navigation mode")
- override fun statusBarLayerPositionAtStartAndEnd() {
- }
+ override fun statusBarLayerPositionAtStartAndEnd() {}
/** {@inheritDoc} */
@Test
@Ignore("Visibility changes depending on orientation and navigation mode")
- override fun statusBarLayerIsVisibleAtStartAndEnd() {
- }
+ override fun statusBarLayerIsVisibleAtStartAndEnd() {}
@Presubmit
@Test
@@ -185,38 +179,38 @@ class OpenImeWindowToOverViewTest(testSpec: FlickerTestParameter) : BaseTest(tes
@Presubmit
@Test
fun statusBarLayerIsVisibleInPortrait() {
- Assume.assumeFalse(testSpec.isLandscapeOrSeascapeAtStart)
- testSpec.statusBarLayerIsVisibleAtStartAndEnd()
+ Assume.assumeFalse(flicker.scenario.isLandscapeOrSeascapeAtStart)
+ flicker.statusBarLayerIsVisibleAtStartAndEnd()
}
@Presubmit
@Test
fun statusBarLayerIsInvisibleInLandscapeShell() {
- Assume.assumeTrue(testSpec.isLandscapeOrSeascapeAtStart)
- Assume.assumeFalse(testSpec.isTablet)
+ Assume.assumeTrue(flicker.scenario.isLandscapeOrSeascapeAtStart)
+ Assume.assumeFalse(flicker.scenario.isTablet)
Assume.assumeTrue(isShellTransitionsEnabled)
- testSpec.assertLayersStart { this.isVisible(ComponentNameMatcher.STATUS_BAR) }
- testSpec.assertLayersEnd { this.isInvisible(ComponentNameMatcher.STATUS_BAR) }
+ flicker.assertLayersStart { this.isVisible(ComponentNameMatcher.STATUS_BAR) }
+ flicker.assertLayersEnd { this.isInvisible(ComponentNameMatcher.STATUS_BAR) }
}
@Presubmit
@Test
fun statusBarLayerIsVisibleInLandscapeLegacy() {
- Assume.assumeTrue(testSpec.isLandscapeOrSeascapeAtStart)
- Assume.assumeTrue(testSpec.isTablet)
+ Assume.assumeTrue(flicker.scenario.isLandscapeOrSeascapeAtStart)
+ Assume.assumeTrue(flicker.scenario.isTablet)
Assume.assumeFalse(isShellTransitionsEnabled)
- testSpec.statusBarLayerIsVisibleAtStartAndEnd()
+ flicker.statusBarLayerIsVisibleAtStartAndEnd()
}
@Presubmit
@Test
fun imeLayerIsVisibleAndAssociatedWithAppWidow() {
- testSpec.assertLayersStart {
+ flicker.assertLayersStart {
isVisible(ComponentNameMatcher.IME)
.visibleRegion(ComponentNameMatcher.IME)
.coversAtMost(isVisible(imeTestApp).visibleRegion(imeTestApp).region)
}
- testSpec.assertLayers {
+ flicker.assertLayers {
this.invoke("imeLayerIsVisibleAndAlignAppWidow") {
val imeVisibleRegion = it.visibleRegion(ComponentNameMatcher.IME)
val appVisibleRegion = it.visibleRegion(imeTestApp)
@@ -232,21 +226,13 @@ class OpenImeWindowToOverViewTest(testSpec: FlickerTestParameter) : BaseTest(tes
/**
* Creates the test configurations.
*
- * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
- * screen orientation and navigation modes.
+ * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(
- supportedRotations = listOf(Surface.ROTATION_0, Surface.ROTATION_90),
- supportedNavigationModes =
- listOf(
- WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY,
- WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
- )
- )
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests()
}
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt
index f810fbb2e3ec..38791a24ad00 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt
@@ -17,17 +17,17 @@
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.BaseTest
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
import com.android.server.wm.flicker.helpers.reopenAppFromOverview
import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.traces.common.ComponentNameMatcher
+import com.android.server.wm.traces.common.service.PlatformConsts
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -41,8 +41,8 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class ReOpenImeWindowTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
- private val testApp = ImeAppAutoFocusHelper(instrumentation, testSpec.startRotation)
+open class ReOpenImeWindowTest(flicker: FlickerTest) : BaseTest(flicker) {
+ private val testApp = ImeAppAutoFocusHelper(instrumentation, flicker.scenario.startRotation)
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit = {
@@ -50,7 +50,7 @@ open class ReOpenImeWindowTest(testSpec: FlickerTestParameter) : BaseTest(testSp
tapl.workspace.switchToOverview().dismissAllTasks()
testApp.launchViaIntent(wmHelper)
testApp.openIME(wmHelper)
- this.setRotation(testSpec.startRotation)
+ this.setRotation(flicker.scenario.startRotation)
device.pressRecentApps()
wmHelper.StateSyncBuilder().withRecentsActivityVisible().waitForAndVerify()
}
@@ -68,7 +68,7 @@ open class ReOpenImeWindowTest(testSpec: FlickerTestParameter) : BaseTest(testSp
// depends on how much of the animation transactions are sent to SF at once
// sometimes this layer appears for 2-3 frames, sometimes for only 1
val recentTaskComponent = ComponentNameMatcher("", "RecentTaskScreenshotSurface")
- testSpec.assertLayers {
+ flicker.assertLayers {
this.visibleLayersShownMoreThanOneConsecutiveEntry(
listOf(
ComponentNameMatcher.SPLASH_SCREEN,
@@ -84,14 +84,14 @@ open class ReOpenImeWindowTest(testSpec: FlickerTestParameter) : BaseTest(testSp
@Test
override fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
val component = ComponentNameMatcher("", "RecentTaskScreenshotSurface")
- testSpec.assertWm {
+ flicker.assertWm {
this.visibleWindowsShownMoreThanOneConsecutiveEntry(
ignoreWindows =
- listOf(
- ComponentNameMatcher.SPLASH_SCREEN,
- ComponentNameMatcher.SNAPSHOT,
- component
- )
+ listOf(
+ ComponentNameMatcher.SPLASH_SCREEN,
+ ComponentNameMatcher.SNAPSHOT,
+ component
+ )
)
}
}
@@ -99,16 +99,14 @@ open class ReOpenImeWindowTest(testSpec: FlickerTestParameter) : BaseTest(testSp
@Presubmit
@Test
fun launcherWindowBecomesInvisible() {
- testSpec.assertWm {
+ flicker.assertWm {
this.isAppWindowVisible(ComponentNameMatcher.LAUNCHER)
.then()
.isAppWindowInvisible(ComponentNameMatcher.LAUNCHER)
}
}
- @Presubmit
- @Test
- fun imeWindowIsAlwaysVisible() = testSpec.imeWindowIsAlwaysVisible()
+ @Presubmit @Test fun imeWindowIsAlwaysVisible() = flicker.imeWindowIsAlwaysVisible()
@Presubmit
@Test
@@ -117,19 +115,19 @@ open class ReOpenImeWindowTest(testSpec: FlickerTestParameter) : BaseTest(testSp
// and exiting overview. Since we log 1x per frame, sometimes the activity visibility
// and the app visibility are updated together, sometimes not, thus ignore activity
// check at the start
- testSpec.assertWm { this.isAppWindowVisible(testApp) }
+ flicker.assertWm { this.isAppWindowVisible(testApp) }
}
@Presubmit
@Test
fun imeLayerBecomesVisible() {
- testSpec.assertLayers { this.isVisible(ComponentNameMatcher.IME) }
+ flicker.assertLayers { this.isVisible(ComponentNameMatcher.IME) }
}
@Presubmit
@Test
fun appLayerReplacesLauncher() {
- testSpec.assertLayers {
+ flicker.assertLayers {
this.isVisible(ComponentNameMatcher.LAUNCHER)
.then()
.isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
@@ -141,9 +139,10 @@ open class ReOpenImeWindowTest(testSpec: FlickerTestParameter) : BaseTest(testSp
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0))
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
+ )
}
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt
index 0ca6457379e1..a6bbf5489663 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt
@@ -19,19 +19,18 @@ package com.android.server.wm.flicker.ime
import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import android.view.WindowManagerPolicyConstants
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.BaseTest
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
import com.android.server.wm.flicker.helpers.SimpleAppHelper
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.traces.common.ComponentNameMatcher
+import com.android.server.wm.traces.common.service.PlatformConsts
import org.junit.Assume
import org.junit.Before
import org.junit.FixMethodOrder
@@ -49,9 +48,9 @@ import org.junit.runners.Parameterized
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Presubmit
-open class SwitchImeWindowsFromGestureNavTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
+open class SwitchImeWindowsFromGestureNavTest(flicker: FlickerTest) : BaseTest(flicker) {
private val testApp = SimpleAppHelper(instrumentation)
- private val imeTestApp = ImeAppAutoFocusHelper(instrumentation, testSpec.startRotation)
+ private val imeTestApp = ImeAppAutoFocusHelper(instrumentation, flicker.scenario.startRotation)
@Before
open fun before() {
@@ -63,7 +62,7 @@ open class SwitchImeWindowsFromGestureNavTest(testSpec: FlickerTestParameter) :
setup {
tapl.setExpectedRotationCheckEnabled(false)
tapl.setIgnoreTaskbarVisibility(true)
- this.setRotation(testSpec.startRotation)
+ this.setRotation(flicker.scenario.startRotation)
testApp.launchViaIntent(wmHelper)
wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify()
@@ -143,7 +142,7 @@ open class SwitchImeWindowsFromGestureNavTest(testSpec: FlickerTestParameter) :
@Presubmit
@Test
fun imeAppWindowVisibility() {
- testSpec.assertWm {
+ flicker.assertWm {
isAppWindowVisible(imeTestApp)
.then()
.isAppSnapshotStartingWindowVisibleFor(testApp, isOptional = true)
@@ -159,27 +158,25 @@ open class SwitchImeWindowsFromGestureNavTest(testSpec: FlickerTestParameter) :
@FlakyTest(bugId = 244414110)
@Test
open fun imeLayerIsVisibleWhenSwitchingToImeApp() {
- testSpec.assertLayersStart { isVisible(ComponentNameMatcher.IME) }
- testSpec.assertLayersTag(TAG_IME_VISIBLE) { isVisible(ComponentNameMatcher.IME) }
- testSpec.assertLayersEnd { isVisible(ComponentNameMatcher.IME) }
+ flicker.assertLayersStart { isVisible(ComponentNameMatcher.IME) }
+ flicker.assertLayersTag(TAG_IME_VISIBLE) { isVisible(ComponentNameMatcher.IME) }
+ flicker.assertLayersEnd { isVisible(ComponentNameMatcher.IME) }
}
@Presubmit
@Test
fun imeLayerIsInvisibleWhenSwitchingToTestApp() {
- testSpec.assertLayersTag(TAG_IME_INVISIBLE) { isInvisible(ComponentNameMatcher.IME) }
+ flicker.assertLayersTag(TAG_IME_INVISIBLE) { isInvisible(ComponentNameMatcher.IME) }
}
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(
- supportedNavigationModes =
- listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY),
- supportedRotations = listOf(Surface.ROTATION_0)
- )
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ supportedNavigationModes = listOf(PlatformConsts.NavBar.MODE_GESTURAL),
+ supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
+ )
}
private const val TAG_IME_VISIBLE = "imeVisible"
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest_ShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest_ShellTransit.kt
index 80ab01624703..c599b10e0084 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest_ShellTransit.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest_ShellTransit.kt
@@ -18,10 +18,11 @@ package com.android.server.wm.flicker.ime
import android.platform.test.annotations.Presubmit
import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.navBarWindowIsVisibleAtStartAndEnd
+import com.android.server.wm.traces.common.ComponentNameMatcher
import org.junit.Assume
import org.junit.Before
import org.junit.FixMethodOrder
@@ -39,16 +40,14 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class SwitchImeWindowsFromGestureNavTest_ShellTransit(testSpec: FlickerTestParameter) :
- SwitchImeWindowsFromGestureNavTest(testSpec) {
+class SwitchImeWindowsFromGestureNavTest_ShellTransit(flicker: FlickerTest) :
+ SwitchImeWindowsFromGestureNavTest(flicker) {
@Before
override fun before() {
Assume.assumeTrue(isShellTransitionsEnabled)
}
- @Presubmit
- @Test
- override fun entireScreenCovered() = super.entireScreenCovered()
+ @Presubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
@Presubmit
@Test
@@ -71,13 +70,13 @@ class SwitchImeWindowsFromGestureNavTest_ShellTransit(testSpec: FlickerTestParam
override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
/**
- * Checks that [ComponentMatcher.NAV_BAR] window is visible and above the app windows at the
+ * Checks that [ComponentNameMatcher.NAV_BAR] window is visible and above the app windows at the
* start and end of the WM trace
*/
@Presubmit
@Test
fun navBarWindowIsVisibleAtStartAndEnd() {
- Assume.assumeFalse(testSpec.isTablet)
- testSpec.navBarWindowIsVisibleAtStartAndEnd()
+ Assume.assumeFalse(flicker.scenario.isTablet)
+ flicker.navBarWindowIsVisibleAtStartAndEnd()
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt
index 49bf86d0c266..5e50b455e4da 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt
@@ -20,11 +20,11 @@ import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.BaseTest
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.helpers.TwoActivitiesAppHelper
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.testapp.ActivityOptions
import com.android.server.wm.traces.common.ComponentNameMatcher
import com.android.server.wm.traces.parser.toFlickerComponent
@@ -57,13 +57,13 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ActivitiesTransitionTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
+class ActivitiesTransitionTest(flicker: FlickerTest) : BaseTest(flicker) {
private val testApp: TwoActivitiesAppHelper = TwoActivitiesAppHelper(instrumentation)
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit = {
setup {
- tapl.setExpectedRotation(testSpec.startRotation)
+ tapl.setExpectedRotation(flicker.scenario.startRotation.value)
testApp.launchViaIntent(wmHelper)
}
teardown { testApp.exit(wmHelper) }
@@ -91,7 +91,7 @@ class ActivitiesTransitionTest(testSpec: FlickerTestParameter) : BaseTest(testSp
ActivityOptions.LaunchNewActivity.COMPONENT.toFlickerComponent()
val imeAutoFocusActivityComponent =
ActivityOptions.SimpleActivity.COMPONENT.toFlickerComponent()
- testSpec.assertWm {
+ flicker.assertWm {
this.isAppWindowOnTop(buttonActivityComponent)
.then()
.isAppWindowOnTop(imeAutoFocusActivityComponent)
@@ -108,7 +108,7 @@ class ActivitiesTransitionTest(testSpec: FlickerTestParameter) : BaseTest(testSp
@Presubmit
@Test
fun launcherWindowNotOnTop() {
- testSpec.assertWm { this.isAppWindowNotOnTop(ComponentNameMatcher.LAUNCHER) }
+ flicker.assertWm { this.isAppWindowNotOnTop(ComponentNameMatcher.LAUNCHER) }
}
/**
@@ -117,20 +117,20 @@ class ActivitiesTransitionTest(testSpec: FlickerTestParameter) : BaseTest(testSp
@Presubmit
@Test
fun launcherLayerNotVisible() {
- testSpec.assertLayers { this.isInvisible(ComponentNameMatcher.LAUNCHER) }
+ flicker.assertLayers { this.isInvisible(ComponentNameMatcher.LAUNCHER) }
}
companion object {
/**
* Creates the test configurations.
*
- * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
- * screen orientation and navigation modes.
+ * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests()
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests()
}
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest.kt
index d0d7bbb42330..14a6668fdc9a 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest.kt
@@ -17,12 +17,12 @@
package com.android.server.wm.flicker.launch
import android.platform.test.annotations.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.helpers.CameraAppHelper
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import org.junit.Assume
import org.junit.Before
import org.junit.FixMethodOrder
@@ -41,8 +41,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class OpenAppAfterCameraTest(testSpec: FlickerTestParameter) :
- OpenAppFromLauncherTransition(testSpec) {
+open class OpenAppAfterCameraTest(flicker: FlickerTest) : OpenAppFromLauncherTransition(flicker) {
@Before
open fun before() {
Assume.assumeFalse(isShellTransitionsEnabled)
@@ -69,13 +68,13 @@ open class OpenAppAfterCameraTest(testSpec: FlickerTestParameter) :
/**
* Creates the test configurations.
*
- * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
- * screen orientation and navigation modes.
+ * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests()
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests()
}
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest_ShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest_ShellTransit.kt
index 56869655be98..99574ef832b6 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest_ShellTransit.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest_ShellTransit.kt
@@ -18,9 +18,9 @@ package com.android.server.wm.flicker.launch
import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import org.junit.Assume
import org.junit.Before
import org.junit.FixMethodOrder
@@ -40,8 +40,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class OpenAppAfterCameraTest_ShellTransit(testSpec: FlickerTestParameter) :
- OpenAppAfterCameraTest(testSpec) {
+class OpenAppAfterCameraTest_ShellTransit(flicker: FlickerTest) : OpenAppAfterCameraTest(flicker) {
@Before
override fun before() {
Assume.assumeTrue(isShellTransitionsEnabled)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt
index 5e6fc2129a6a..e0df5be9bac1 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt
@@ -18,13 +18,12 @@ package com.android.server.wm.flicker.launch
import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.RequiresDevice
-import android.view.Surface
-import android.view.WindowManagerPolicyConstants
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule
+import com.android.server.wm.traces.common.service.PlatformConsts
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -55,17 +54,16 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class OpenAppColdFromIcon(testSpec: FlickerTestParameter) :
- OpenAppFromLauncherTransition(testSpec) {
+class OpenAppColdFromIcon(flicker: FlickerTest) : OpenAppFromLauncherTransition(flicker) {
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit
get() = {
super.transition(this)
setup {
- if (testSpec.isTablet) {
- tapl.setExpectedRotation(testSpec.startRotation)
+ if (flicker.scenario.isTablet) {
+ tapl.setExpectedRotation(flicker.scenario.startRotation.value)
} else {
- tapl.setExpectedRotation(Surface.ROTATION_0)
+ tapl.setExpectedRotation(PlatformConsts.Rotation.ROTATION_0.value)
}
RemoveAllTasksButHomeRule.removeAllTasksButHome()
}
@@ -180,19 +178,16 @@ class OpenAppColdFromIcon(testSpec: FlickerTestParameter) :
/**
* Creates the test configurations.
*
- * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
- * screen orientation and navigation modes.
+ * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance()
- // TAPL fails on landscape mode b/240916028
- .getConfigNonRotationTests(
- supportedNavigationModes = listOf(
- WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY
- )
- )
+ fun getParams(): Collection<FlickerTest> {
+ // TAPL fails on landscape mode b/240916028
+ return FlickerTestFactory.nonRotationTests(
+ supportedNavigationModes = listOf(PlatformConsts.NavBar.MODE_3BUTTON)
+ )
}
}
}
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 7576ab94284b..66af72ede90c 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
@@ -19,12 +19,12 @@ package com.android.server.wm.flicker.launch
import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
import android.platform.test.annotations.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.annotation.FlickerServiceCompatible
-import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome
import org.junit.FixMethodOrder
import org.junit.Test
@@ -57,15 +57,14 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class OpenAppColdTest(testSpec: FlickerTestParameter) :
- OpenAppFromLauncherTransition(testSpec) {
+open class OpenAppColdTest(flicker: FlickerTest) : OpenAppFromLauncherTransition(flicker) {
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit
get() = {
super.transition(this)
setup {
removeAllTasksButHome()
- this.setRotation(testSpec.startRotation)
+ this.setRotation(flicker.scenario.startRotation)
}
teardown { testApp.exit(wmHelper) }
transitions { testApp.launchViaIntent(wmHelper) }
@@ -83,13 +82,13 @@ open class OpenAppColdTest(testSpec: FlickerTestParameter) :
/**
* Creates the test configurations.
*
- * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
- * screen orientation and navigation modes.
+ * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests()
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests()
}
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLauncherTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLauncherTransition.kt
index 23748bef96fd..b234ec7828ae 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLauncherTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLauncherTransition.kt
@@ -17,28 +17,27 @@
package com.android.server.wm.flicker.launch
import android.platform.test.annotations.Presubmit
-import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.replacesLayer
import com.android.server.wm.traces.common.ComponentNameMatcher
import org.junit.Test
/** Base class for app launch tests */
-abstract class OpenAppFromLauncherTransition(testSpec: FlickerTestParameter) :
- OpenAppTransition(testSpec) {
+abstract class OpenAppFromLauncherTransition(flicker: FlickerTest) : OpenAppTransition(flicker) {
- /** Checks that the focus changes from the [ComponentMatcher.LAUNCHER] to [testApp] */
+ /** Checks that the focus changes from the [ComponentNameMatcher.LAUNCHER] to [testApp] */
@Presubmit
@Test
open fun focusChanges() {
- testSpec.assertEventLog { this.focusChanges("NexusLauncherActivity", testApp.`package`) }
+ flicker.assertEventLog { this.focusChanges("NexusLauncherActivity", testApp.`package`) }
}
/**
- * Checks that [ComponentMatcher.LAUNCHER] layer is visible at the start of the transition, and
- * is replaced by [testApp], which remains visible until the end
+ * Checks that [ComponentNameMatcher.LAUNCHER] layer is visible at the start of the transition,
+ * and is replaced by [testApp], which remains visible until the end
*/
open fun appLayerReplacesLauncher() {
- testSpec.replacesLayer(
+ flicker.replacesLayer(
ComponentNameMatcher.LAUNCHER,
testApp,
ignoreEntriesWithRotationLayer = true,
@@ -48,14 +47,14 @@ abstract class OpenAppFromLauncherTransition(testSpec: FlickerTestParameter) :
}
/**
- * Checks that [ComponentMatcher.LAUNCHER] window is the top window at the start of the
- * transition, and is replaced by a [ComponentMatcher.SNAPSHOT] or
- * [ComponentMatcher.SPLASH_SCREEN], or [testApp], which remains visible until the end
+ * Checks that [ComponentNameMatcher.LAUNCHER] window is the top window at the start of the
+ * transition, and is replaced by a [ComponentNameMatcher.SNAPSHOT] or
+ * [ComponentNameMatcher.SPLASH_SCREEN], or [testApp], which remains visible until the end
*/
@Presubmit
@Test
open fun appWindowReplacesLauncherAsTopWindow() {
- testSpec.assertWm {
+ flicker.assertWm {
this.isAppWindowOnTop(ComponentNameMatcher.LAUNCHER)
.then()
.isAppWindowOnTop(
@@ -68,6 +67,6 @@ abstract class OpenAppFromLauncherTransition(testSpec: FlickerTestParameter) :
@Presubmit
@Test
open fun appWindowAsTopWindowAtEnd() {
- testSpec.assertWmEnd { this.isAppWindowOnTop(testApp) }
+ flicker.assertWmEnd { this.isAppWindowOnTop(testApp) }
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt
index 0edbc86ab65f..f5f71904f092 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt
@@ -20,10 +20,10 @@ import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import android.platform.test.annotations.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.traces.common.ComponentNameMatcher
import org.junit.FixMethodOrder
import org.junit.Ignore
@@ -44,8 +44,8 @@ import org.junit.runners.Parameterized
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Postsubmit
-open class OpenAppFromLockNotificationCold(testSpec: FlickerTestParameter) :
- OpenAppFromNotificationCold(testSpec) {
+open class OpenAppFromLockNotificationCold(flicker: FlickerTest) :
+ OpenAppFromNotificationCold(flicker) {
override val openingNotificationsFromLockScreen = true
@@ -93,8 +93,9 @@ open class OpenAppFromLockNotificationCold(testSpec: FlickerTestParameter) :
* Checks the position of the [ComponentNameMatcher.STATUS_BAR] at the start and end of the
* transition
*/
- @Presubmit @Test override fun statusBarLayerPositionAtEnd() =
- super.statusBarLayerPositionAtEnd()
+ @Presubmit
+ @Test
+ override fun statusBarLayerPositionAtEnd() = super.statusBarLayerPositionAtEnd()
/** {@inheritDoc} */
@Test
@@ -119,13 +120,13 @@ open class OpenAppFromLockNotificationCold(testSpec: FlickerTestParameter) :
/**
* Creates the test configurations.
*
- * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
- * screen orientation and navigation modes.
+ * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests()
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests()
}
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWarm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWarm.kt
index 5a7b8b9e3d9a..fe49c61049c9 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWarm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWarm.kt
@@ -19,10 +19,10 @@ package com.android.server.wm.flicker.launch
import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
import android.platform.test.annotations.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.statusBarLayerPositionAtEnd
import com.android.server.wm.traces.common.ComponentNameMatcher
import org.junit.FixMethodOrder
@@ -43,8 +43,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class OpenAppFromLockNotificationWarm(testSpec: FlickerTestParameter) :
- OpenAppFromNotificationWarm(testSpec) {
+class OpenAppFromLockNotificationWarm(flicker: FlickerTest) : OpenAppFromNotificationWarm(flicker) {
override val openingNotificationsFromLockScreen = true
@@ -70,7 +69,7 @@ class OpenAppFromLockNotificationWarm(testSpec: FlickerTestParameter) :
@Test
@Presubmit
fun appWindowBecomesFirstAndOnlyTopWindow() {
- testSpec.assertWm {
+ flicker.assertWm {
this.hasNoVisibleAppWindow()
.then()
.isAppWindowOnTop(ComponentNameMatcher.SNAPSHOT, isOptional = true)
@@ -85,7 +84,7 @@ class OpenAppFromLockNotificationWarm(testSpec: FlickerTestParameter) :
@Test
@Presubmit
fun screenLockedStart() {
- testSpec.assertWmStart { isKeyguardShowing() }
+ flicker.assertWmStart { isKeyguardShowing() }
}
/** {@inheritDoc} */
@@ -108,7 +107,7 @@ class OpenAppFromLockNotificationWarm(testSpec: FlickerTestParameter) :
* Checks the position of the [ComponentNameMatcher.STATUS_BAR] at the start and end of the
* transition
*/
- @Presubmit @Test fun statusBarLayerPositionAtEnd() = testSpec.statusBarLayerPositionAtEnd()
+ @Presubmit @Test fun statusBarLayerPositionAtEnd() = flicker.statusBarLayerPositionAtEnd()
/** {@inheritDoc} */
@Test
@@ -133,13 +132,13 @@ class OpenAppFromLockNotificationWarm(testSpec: FlickerTestParameter) :
/**
* Creates the test configurations.
*
- * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
- * screen orientation and navigation modes.
+ * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests()
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests()
}
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWithLockOverlayApp.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWithLockOverlayApp.kt
index 4ee12837fe09..d9a3ad2b3d6d 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWithLockOverlayApp.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWithLockOverlayApp.kt
@@ -20,12 +20,12 @@ import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import android.platform.test.annotations.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.helpers.ShowWhenLockedAppHelper
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.traces.common.ComponentNameMatcher
import org.junit.FixMethodOrder
import org.junit.Test
@@ -44,8 +44,8 @@ import org.junit.runners.Parameterized
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Postsubmit
-class OpenAppFromLockNotificationWithLockOverlayApp(testSpec: FlickerTestParameter) :
- OpenAppFromLockNotificationCold(testSpec) {
+class OpenAppFromLockNotificationWithLockOverlayApp(flicker: FlickerTest) :
+ OpenAppFromLockNotificationCold(flicker) {
private val showWhenLockedApp: ShowWhenLockedAppHelper =
ShowWhenLockedAppHelper(instrumentation)
@@ -74,7 +74,7 @@ class OpenAppFromLockNotificationWithLockOverlayApp(testSpec: FlickerTestParamet
@Test
@FlakyTest(bugId = 227143265)
fun showWhenLockedAppWindowBecomesVisible() {
- testSpec.assertWm {
+ flicker.assertWm {
this.hasNoVisibleAppWindow()
.then()
.isAppWindowOnTop(ComponentNameMatcher.SNAPSHOT, isOptional = true)
@@ -86,7 +86,7 @@ class OpenAppFromLockNotificationWithLockOverlayApp(testSpec: FlickerTestParamet
@Test
@FlakyTest(bugId = 227143265)
fun showWhenLockedAppLayerBecomesVisible() {
- testSpec.assertLayers {
+ flicker.assertLayers {
this.isInvisible(showWhenLockedApp)
.then()
.isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
@@ -107,20 +107,19 @@ class OpenAppFromLockNotificationWithLockOverlayApp(testSpec: FlickerTestParamet
/** {@inheritDoc} */
@FlakyTest(bugId = 209599395)
@Test
- override fun navBarLayerIsVisibleAtStartAndEnd() =
- super.navBarLayerIsVisibleAtStartAndEnd()
+ override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
companion object {
/**
* Creates the test configurations.
*
- * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
- * screen orientation and navigation modes.
+ * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests()
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests()
}
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockTransition.kt
index 3cc23909b603..718c6e9da41a 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockTransition.kt
@@ -18,8 +18,8 @@ package com.android.server.wm.flicker.launch
import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.navBarLayerPositionAtEnd
import com.android.server.wm.flicker.statusBarLayerPositionAtEnd
import com.android.server.wm.traces.common.ComponentNameMatcher
@@ -28,8 +28,7 @@ import org.junit.Ignore
import org.junit.Test
/** Base class for app launch tests from lock screen */
-abstract class OpenAppFromLockTransition(testSpec: FlickerTestParameter) :
- OpenAppTransition(testSpec) {
+abstract class OpenAppFromLockTransition(flicker: FlickerTest) : OpenAppTransition(flicker) {
/** Defines the transition used to run the test */
override val transition: FlickerBuilder.() -> Unit = {
@@ -46,7 +45,7 @@ abstract class OpenAppFromLockTransition(testSpec: FlickerTestParameter) :
@Presubmit
@Test
open fun focusChanges() {
- testSpec.assertEventLog { this.focusChanges("", testApp.`package`) }
+ flicker.assertEventLog { this.focusChanges("", testApp.`package`) }
}
/**
@@ -56,7 +55,7 @@ abstract class OpenAppFromLockTransition(testSpec: FlickerTestParameter) :
@FlakyTest(bugId = 203538234)
@Test
open fun appWindowBecomesFirstAndOnlyTopWindow() {
- testSpec.assertWm {
+ flicker.assertWm {
this.hasNoVisibleAppWindow()
.then()
.isAppWindowOnTop(ComponentNameMatcher.SNAPSHOT, isOptional = true)
@@ -71,7 +70,7 @@ abstract class OpenAppFromLockTransition(testSpec: FlickerTestParameter) :
@Presubmit
@Test
fun screenLockedStart() {
- testSpec.assertLayersStart { isEmpty() }
+ flicker.assertLayersStart { isEmpty() }
}
/** {@inheritDoc} */
@@ -99,16 +98,16 @@ abstract class OpenAppFromLockTransition(testSpec: FlickerTestParameter) :
@Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
override fun taskBarWindowIsAlwaysVisible() {}
- /** Checks the position of the [ComponentMatcher.NAV_BAR] at the end of the transition */
+ /** Checks the position of the [ComponentNameMatcher.NAV_BAR] at the end of the transition */
@Presubmit
@Test
open fun navBarLayerPositionAtEnd() {
- Assume.assumeFalse(testSpec.isTablet)
- testSpec.navBarLayerPositionAtEnd()
+ Assume.assumeFalse(flicker.scenario.isTablet)
+ flicker.navBarLayerPositionAtEnd()
}
- /** Checks the position of the [ComponentMatcher.STATUS_BAR] at the end of the transition */
- @Presubmit @Test fun statusBarLayerPositionAtEnd() = testSpec.statusBarLayerPositionAtEnd()
+ /** Checks the position of the [ComponentNameMatcher.STATUS_BAR] at the end of the transition */
+ @Presubmit @Test fun statusBarLayerPositionAtEnd() = flicker.statusBarLayerPositionAtEnd()
/** {@inheritDoc} */
@Test
@@ -116,13 +115,13 @@ abstract class OpenAppFromLockTransition(testSpec: FlickerTestParameter) :
override fun statusBarLayerIsVisibleAtStartAndEnd() {}
/**
- * Checks that the [ComponentMatcher.STATUS_BAR] layer is visible at the end of the trace
+ * Checks that the [ComponentNameMatcher.STATUS_BAR] layer is visible at the end of the trace
*
* It is not possible to check at the start because the screen is off
*/
@Presubmit
@Test
fun statusBarLayerIsVisibleAtEnd() {
- testSpec.assertLayersEnd { this.isVisible(ComponentNameMatcher.STATUS_BAR) }
+ flicker.assertLayersEnd { this.isVisible(ComponentNameMatcher.STATUS_BAR) }
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationCold.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationCold.kt
index 8dd94cd0ef15..240e90b9f019 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationCold.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationCold.kt
@@ -19,10 +19,10 @@ package com.android.server.wm.flicker.launch
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import android.platform.test.annotations.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.statusBarLayerPositionAtEnd
import com.android.server.wm.traces.common.ComponentNameMatcher
import org.junit.FixMethodOrder
@@ -44,8 +44,8 @@ import org.junit.runners.Parameterized
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Postsubmit
-open class OpenAppFromNotificationCold(testSpec: FlickerTestParameter) :
- OpenAppFromNotificationWarm(testSpec) {
+open class OpenAppFromNotificationCold(flicker: FlickerTest) :
+ OpenAppFromNotificationWarm(flicker) {
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit
get() = {
@@ -61,13 +61,9 @@ open class OpenAppFromNotificationCold(testSpec: FlickerTestParameter) :
}
}
- @Postsubmit
- @Test
- override fun appWindowBecomesVisible() = appWindowBecomesVisible_coldStart()
+ @Postsubmit @Test override fun appWindowBecomesVisible() = appWindowBecomesVisible_coldStart()
- @Postsubmit
- @Test
- override fun appLayerBecomesVisible() = appLayerBecomesVisible_coldStart()
+ @Postsubmit @Test override fun appLayerBecomesVisible() = appLayerBecomesVisible_coldStart()
/** {@inheritDoc} */
@Test
@@ -89,9 +85,7 @@ open class OpenAppFromNotificationCold(testSpec: FlickerTestParameter) :
* Checks the position of the [ComponentNameMatcher.STATUS_BAR] at the start and end of the
* transition
*/
- @Presubmit
- @Test
- open fun statusBarLayerPositionAtEnd() = testSpec.statusBarLayerPositionAtEnd()
+ @Presubmit @Test open fun statusBarLayerPositionAtEnd() = flicker.statusBarLayerPositionAtEnd()
/** {@inheritDoc} */
@Test
@@ -107,13 +101,13 @@ open class OpenAppFromNotificationCold(testSpec: FlickerTestParameter) :
/**
* Creates the test configurations.
*
- * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
- * screen orientation and navigation modes.
+ * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests()
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests()
}
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarm.kt
index db48b3f4a48c..6388a5ae2259 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarm.kt
@@ -24,13 +24,13 @@ import android.view.WindowInsets
import android.view.WindowManager
import androidx.test.uiautomator.By
import androidx.test.uiautomator.Until
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.helpers.NotificationAppHelper
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.navBarLayerIsVisibleAtEnd
import com.android.server.wm.flicker.navBarLayerPositionAtEnd
import com.android.server.wm.flicker.navBarWindowIsVisibleAtEnd
@@ -56,8 +56,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class OpenAppFromNotificationWarm(testSpec: FlickerTestParameter) :
- OpenAppTransition(testSpec) {
+open class OpenAppFromNotificationWarm(flicker: FlickerTest) : OpenAppTransition(flicker) {
override val testApp: NotificationAppHelper = NotificationAppHelper(instrumentation)
open val openingNotificationsFromLockScreen = false
@@ -67,7 +66,7 @@ open class OpenAppFromNotificationWarm(testSpec: FlickerTestParameter) :
get() = {
setup {
device.wakeUpAndGoToHomeScreen()
- this.setRotation(testSpec.startRotation)
+ this.setRotation(flicker.scenario.startRotation)
testApp.launchViaIntent(wmHelper)
wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify()
testApp.postNotification(wmHelper)
@@ -120,19 +119,19 @@ open class OpenAppFromNotificationWarm(testSpec: FlickerTestParameter) :
@Presubmit
@Test
open fun notificationAppWindowVisibleAtEnd() {
- testSpec.assertWmEnd { this.isAppWindowVisible(testApp) }
+ flicker.assertWmEnd { this.isAppWindowVisible(testApp) }
}
@Presubmit
@Test
open fun notificationAppWindowOnTopAtEnd() {
- testSpec.assertWmEnd { this.isAppWindowOnTop(testApp) }
+ flicker.assertWmEnd { this.isAppWindowOnTop(testApp) }
}
@Presubmit
@Test
open fun notificationAppLayerVisibleAtEnd() {
- testSpec.assertLayersEnd { this.isVisible(testApp) }
+ flicker.assertLayersEnd { this.isVisible(testApp) }
}
/**
@@ -144,8 +143,8 @@ open class OpenAppFromNotificationWarm(testSpec: FlickerTestParameter) :
@Presubmit
@Test
open fun taskBarWindowIsVisibleAtEnd() {
- Assume.assumeTrue(testSpec.isTablet)
- testSpec.taskBarWindowIsVisibleAtEnd()
+ Assume.assumeTrue(flicker.scenario.isTablet)
+ flicker.taskBarWindowIsVisibleAtEnd()
}
/**
@@ -156,31 +155,31 @@ open class OpenAppFromNotificationWarm(testSpec: FlickerTestParameter) :
@Presubmit
@Test
open fun taskBarLayerIsVisibleAtEnd() {
- Assume.assumeTrue(testSpec.isTablet)
- testSpec.taskBarLayerIsVisibleAtEnd()
+ Assume.assumeTrue(flicker.scenario.isTablet)
+ flicker.taskBarLayerIsVisibleAtEnd()
}
/** Checks the position of the [ComponentNameMatcher.NAV_BAR] at the end of the transition */
@Presubmit
@Test
open fun navBarLayerPositionAtEnd() {
- Assume.assumeFalse(testSpec.isTablet)
- testSpec.navBarLayerPositionAtEnd()
+ Assume.assumeFalse(flicker.scenario.isTablet)
+ flicker.navBarLayerPositionAtEnd()
}
/** {@inheritDoc} */
@Presubmit
@Test
open fun navBarLayerIsVisibleAtEnd() {
- Assume.assumeFalse(testSpec.isTablet)
- testSpec.navBarLayerIsVisibleAtEnd()
+ Assume.assumeFalse(flicker.scenario.isTablet)
+ flicker.navBarLayerIsVisibleAtEnd()
}
@Presubmit
@Test
open fun navBarWindowIsVisibleAtEnd() {
- Assume.assumeFalse(testSpec.isTablet)
- testSpec.navBarWindowIsVisibleAtEnd()
+ Assume.assumeFalse(flicker.scenario.isTablet)
+ flicker.navBarWindowIsVisibleAtEnd()
}
/** {@inheritDoc} */
@@ -203,13 +202,13 @@ open class OpenAppFromNotificationWarm(testSpec: FlickerTestParameter) :
/**
* Creates the test configurations.
*
- * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
- * screen orientation and navigation modes.
+ * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests()
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests()
}
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
index fd8a38c3f6b7..9106835fb7b4 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
@@ -19,13 +19,13 @@ package com.android.server.wm.flicker.launch
import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
import android.platform.test.annotations.RequiresDevice
-import android.view.Surface
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.annotation.FlickerServiceCompatible
-import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import com.android.server.wm.traces.common.service.PlatformConsts
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -59,8 +59,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class OpenAppFromOverviewTest(testSpec: FlickerTestParameter) :
- OpenAppFromLauncherTransition(testSpec) {
+open class OpenAppFromOverviewTest(flicker: FlickerTest) : OpenAppFromLauncherTransition(flicker) {
/** Defines the transition used to run the test */
override val transition: FlickerBuilder.() -> Unit
@@ -72,14 +71,14 @@ open class OpenAppFromOverviewTest(testSpec: FlickerTestParameter) :
tapl.goHome()
wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
// By default, launcher doesn't rotate on phones, but rotates on tablets
- if (testSpec.isTablet) {
- tapl.setExpectedRotation(testSpec.startRotation)
+ if (flicker.scenario.isTablet) {
+ tapl.setExpectedRotation(flicker.scenario.startRotation.value)
} else {
- tapl.setExpectedRotation(Surface.ROTATION_0)
+ tapl.setExpectedRotation(PlatformConsts.Rotation.ROTATION_0.value)
}
tapl.workspace.switchToOverview()
wmHelper.StateSyncBuilder().withRecentsActivityVisible().waitForAndVerify()
- this.setRotation(testSpec.startRotation)
+ this.setRotation(flicker.scenario.startRotation)
}
transitions {
tapl.overview.currentTask.open()
@@ -109,13 +108,13 @@ open class OpenAppFromOverviewTest(testSpec: FlickerTestParameter) :
/**
* Creates the test configurations.
*
- * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
- * screen orientation and navigation modes.
+ * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests()
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests()
}
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
index 1ecde46874c1..f295ce30978b 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
@@ -20,14 +20,13 @@ import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import android.platform.test.annotations.RequiresDevice
-import android.view.Surface
-import android.view.WindowManagerPolicyConstants
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.annotation.FlickerServiceCompatible
import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.traces.common.ComponentNameMatcher
+import com.android.server.wm.traces.common.service.PlatformConsts
import org.junit.Assume
import org.junit.FixMethodOrder
import org.junit.Ignore
@@ -63,8 +62,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class OpenAppNonResizeableTest(testSpec: FlickerTestParameter) :
- OpenAppFromLockTransition(testSpec) {
+open class OpenAppNonResizeableTest(flicker: FlickerTest) : OpenAppFromLockTransition(flicker) {
override val testApp = NonResizeableAppHelper(instrumentation)
/**
@@ -74,8 +72,8 @@ open class OpenAppNonResizeableTest(testSpec: FlickerTestParameter) :
@FlakyTest(bugId = 227083463)
@Test
fun navBarLayerVisibilityChanges() {
- Assume.assumeFalse(testSpec.isTablet)
- testSpec.assertLayers {
+ Assume.assumeFalse(flicker.scenario.isTablet)
+ flicker.assertLayers {
this.isInvisible(ComponentNameMatcher.NAV_BAR)
.then()
.isVisible(ComponentNameMatcher.NAV_BAR)
@@ -86,7 +84,7 @@ open class OpenAppNonResizeableTest(testSpec: FlickerTestParameter) :
@Presubmit
@Test
fun appWindowBecomesVisibleAtEnd() {
- testSpec.assertWmEnd { this.isAppWindowVisible(testApp) }
+ flicker.assertWmEnd { this.isAppWindowVisible(testApp) }
}
/**
@@ -96,8 +94,8 @@ open class OpenAppNonResizeableTest(testSpec: FlickerTestParameter) :
@Presubmit
@Test
fun navBarWindowsVisibilityChanges() {
- Assume.assumeFalse(testSpec.isTablet)
- testSpec.assertWm {
+ Assume.assumeFalse(flicker.scenario.isTablet)
+ flicker.assertWm {
this.isNonAppWindowInvisible(ComponentNameMatcher.NAV_BAR)
.then()
.isAboveAppWindowVisible(ComponentNameMatcher.NAV_BAR)
@@ -111,8 +109,8 @@ open class OpenAppNonResizeableTest(testSpec: FlickerTestParameter) :
@Presubmit
@Test
fun taskBarLayerIsVisibleAtEnd() {
- Assume.assumeTrue(testSpec.isTablet)
- testSpec.assertLayersEnd { this.isVisible(ComponentNameMatcher.TASK_BAR) }
+ Assume.assumeTrue(flicker.scenario.isTablet)
+ flicker.assertLayersEnd { this.isVisible(ComponentNameMatcher.TASK_BAR) }
}
/**
@@ -123,45 +121,40 @@ open class OpenAppNonResizeableTest(testSpec: FlickerTestParameter) :
@Presubmit
@Test
override fun statusBarLayerIsVisibleAtStartAndEnd() {
- testSpec.assertLayersEnd { this.isVisible(ComponentNameMatcher.STATUS_BAR) }
+ flicker.assertLayersEnd { this.isVisible(ComponentNameMatcher.STATUS_BAR) }
}
/** {@inheritDoc} */
@Test
@Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
- override fun taskBarLayerIsVisibleAtStartAndEnd() {
- }
+ override fun taskBarLayerIsVisibleAtStartAndEnd() {}
/** {@inheritDoc} */
@Test
@Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
- override fun navBarLayerIsVisibleAtStartAndEnd() {
- }
+ override fun navBarLayerIsVisibleAtStartAndEnd() {}
/** {@inheritDoc} */
@Test
@Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
- override fun taskBarWindowIsAlwaysVisible() {
- }
+ override fun taskBarWindowIsAlwaysVisible() {}
/** {@inheritDoc} */
@Test
@Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
- override fun navBarWindowIsAlwaysVisible() {
- }
+ override fun navBarWindowIsAlwaysVisible() {}
/** {@inheritDoc} */
@Test
@Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
- override fun statusBarWindowIsAlwaysVisible() {
- }
+ override fun statusBarWindowIsAlwaysVisible() {}
/** Checks the [ComponentNameMatcher.NAV_BAR] is visible at the end of the transition */
@Postsubmit
@Test
fun navBarLayerIsVisibleAtEnd() {
- Assume.assumeFalse(testSpec.isTablet)
- testSpec.assertLayersEnd { this.isVisible(ComponentNameMatcher.NAV_BAR) }
+ Assume.assumeFalse(flicker.scenario.isTablet)
+ flicker.assertLayersEnd { this.isVisible(ComponentNameMatcher.NAV_BAR) }
}
/** {@inheritDoc} */
@@ -174,7 +167,7 @@ open class OpenAppNonResizeableTest(testSpec: FlickerTestParameter) :
@Presubmit
@Test
override fun appLayerBecomesVisible() {
- Assume.assumeFalse(testSpec.isTablet)
+ Assume.assumeFalse(flicker.scenario.isTablet)
super.appLayerBecomesVisible()
}
@@ -182,14 +175,12 @@ open class OpenAppNonResizeableTest(testSpec: FlickerTestParameter) :
@FlakyTest(bugId = 227143265)
@Test
fun appLayerBecomesVisibleTablet() {
- Assume.assumeTrue(testSpec.isTablet)
+ Assume.assumeTrue(flicker.scenario.isTablet)
super.appLayerBecomesVisible()
}
/** {@inheritDoc} */
- @FlakyTest
- @Test
- override fun entireScreenCovered() = super.entireScreenCovered()
+ @FlakyTest @Test override fun entireScreenCovered() = super.entireScreenCovered()
@FlakyTest(bugId = 218470989)
@Test
@@ -210,18 +201,16 @@ open class OpenAppNonResizeableTest(testSpec: FlickerTestParameter) :
/**
* Creates the test configurations.
*
- * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
- * screen orientation and navigation modes.
+ * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(
- supportedNavigationModes =
- listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY),
- supportedRotations = listOf(Surface.ROTATION_0)
- )
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ supportedNavigationModes = listOf(PlatformConsts.NavBar.MODE_GESTURAL),
+ supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
+ )
}
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt
index 4fd251aefffc..2adb0b4a5ec7 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt
@@ -18,8 +18,8 @@ package com.android.server.wm.flicker.launch
import android.platform.test.annotations.Presubmit
import com.android.server.wm.flicker.BaseTest
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.helpers.SimpleAppHelper
import com.android.server.wm.flicker.helpers.StandardAppHelper
import com.android.server.wm.flicker.helpers.setRotation
@@ -28,15 +28,15 @@ import com.android.server.wm.traces.common.ComponentNameMatcher
import org.junit.Test
/** Base class for app launch tests */
-abstract class OpenAppTransition(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
+abstract class OpenAppTransition(flicker: FlickerTest) : BaseTest(flicker) {
protected open val testApp: StandardAppHelper = SimpleAppHelper(instrumentation)
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit = {
setup {
- tapl.setExpectedRotation(testSpec.startRotation)
+ tapl.setExpectedRotation(flicker.scenario.startRotation.value)
device.wakeUpAndGoToHomeScreen()
- this.setRotation(testSpec.startRotation)
+ this.setRotation(flicker.scenario.startRotation)
}
teardown { testApp.exit(wmHelper) }
}
@@ -52,7 +52,7 @@ abstract class OpenAppTransition(testSpec: FlickerTestParameter) : BaseTest(test
}
protected fun appLayerBecomesVisible_coldStart() {
- testSpec.assertLayers {
+ flicker.assertLayers {
this.notContains(testApp)
.then()
.isInvisible(testApp, isOptional = true)
@@ -66,7 +66,7 @@ abstract class OpenAppTransition(testSpec: FlickerTestParameter) : BaseTest(test
}
protected fun appLayerBecomesVisible_warmStart() {
- testSpec.assertLayers {
+ flicker.assertLayers {
this.isInvisible(testApp)
.then()
.isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
@@ -87,7 +87,7 @@ abstract class OpenAppTransition(testSpec: FlickerTestParameter) : BaseTest(test
@Presubmit @Test open fun appWindowBecomesVisible() = appWindowBecomesVisible_coldStart()
protected fun appWindowBecomesVisible_coldStart() {
- testSpec.assertWm {
+ flicker.assertWm {
this.notContains(testApp)
.then()
.isAppWindowInvisible(testApp, isOptional = true)
@@ -97,7 +97,7 @@ abstract class OpenAppTransition(testSpec: FlickerTestParameter) : BaseTest(test
}
protected fun appWindowBecomesVisible_warmStart() {
- testSpec.assertWm {
+ flicker.assertWm {
this.isAppWindowInvisible(testApp)
.then()
.isAppWindowVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
@@ -115,7 +115,7 @@ abstract class OpenAppTransition(testSpec: FlickerTestParameter) : BaseTest(test
@Presubmit
@Test
open fun appWindowBecomesTopWindow() {
- testSpec.assertWm {
+ flicker.assertWm {
this.isAppWindowNotOnTop(testApp)
.then()
.isAppWindowOnTop(
@@ -131,6 +131,6 @@ abstract class OpenAppTransition(testSpec: FlickerTestParameter) : BaseTest(test
@Presubmit
@Test
open fun appWindowIsTopWindowAtEnd() {
- testSpec.assertWmEnd { this.isAppWindowOnTop(testApp) }
+ flicker.assertWmEnd { this.isAppWindowOnTop(testApp) }
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
index 03741c8f968c..62d7cc099771 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
@@ -19,12 +19,12 @@ package com.android.server.wm.flicker.launch
import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
import android.platform.test.annotations.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.annotation.FlickerServiceCompatible
-import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -57,8 +57,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class OpenAppWarmTest(testSpec: FlickerTestParameter) :
- OpenAppFromLauncherTransition(testSpec) {
+open class OpenAppWarmTest(flicker: FlickerTest) : OpenAppFromLauncherTransition(flicker) {
/** Defines the transition used to run the test */
override val transition: FlickerBuilder.() -> Unit
get() = {
@@ -68,7 +67,7 @@ open class OpenAppWarmTest(testSpec: FlickerTestParameter) :
testApp.launchViaIntent(wmHelper)
tapl.goHome()
wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
- this.setRotation(testSpec.startRotation)
+ this.setRotation(flicker.scenario.startRotation)
}
teardown { testApp.exit(wmHelper) }
transitions { testApp.launchViaIntent(wmHelper) }
@@ -96,13 +95,13 @@ open class OpenAppWarmTest(testSpec: FlickerTestParameter) :
/**
* Creates the test configurations.
*
- * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
- * screen orientation and navigation modes.
+ * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests()
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests()
}
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt
index e1fd5a769255..b9594a1d10c5 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt
@@ -22,16 +22,16 @@ import android.os.Handler
import android.platform.test.annotations.Presubmit
import androidx.test.filters.RequiresDevice
import androidx.test.platform.app.InstrumentationRegistry
-import com.android.server.wm.flicker.FlickerBuilderProvider
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.R
-import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.SimpleAppHelper
import com.android.server.wm.flicker.helpers.StandardAppHelper
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
+import com.android.server.wm.flicker.junit.FlickerBuilderProvider
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule
import com.android.server.wm.traces.common.ComponentNameMatcher
import com.android.server.wm.traces.common.WindowManagerConditionsFactory
@@ -55,7 +55,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class OverrideTaskTransitionTest(val testSpec: FlickerTestParameter) {
+class OverrideTaskTransitionTest(val flicker: FlickerTest) {
private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
private val testApp: StandardAppHelper = SimpleAppHelper(instrumentation)
@@ -66,7 +66,7 @@ class OverrideTaskTransitionTest(val testSpec: FlickerTestParameter) {
setup {
device.wakeUpAndGoToHomeScreen()
RemoveAllTasksButHomeRule.removeAllTasksButHome()
- setRotation(testSpec.startRotation)
+ setRotation(flicker.scenario.startRotation)
}
transitions {
instrumentation.context.startActivity(
@@ -87,24 +87,24 @@ class OverrideTaskTransitionTest(val testSpec: FlickerTestParameter) {
@Presubmit
@Test
fun testSimpleActivityIsShownDirectly() {
- testSpec.assertLayers {
+ flicker.assertLayers {
// Before the app launches, only the launcher is visible.
isVisible(ComponentNameMatcher.LAUNCHER)
- .isInvisible(testApp)
- .then()
- // Animation starts, but the app may not be drawn yet which means the Splash
- // may be visible.
- .isInvisible(testApp, isOptional = true)
- .isVisible(ComponentNameMatcher.SPLASH_SCREEN, isOptional = true)
- .then()
- // App shows up with the custom animation starting at alpha=1.
- .isVisible(testApp)
- .then()
- // App custom animation continues to alpha=0 (invisible).
- .isInvisible(testApp)
- .then()
- // App custom animation ends with it being visible.
- .isVisible(testApp)
+ .isInvisible(testApp)
+ .then()
+ // Animation starts, but the app may not be drawn yet which means the Splash
+ // may be visible.
+ .isInvisible(testApp, isOptional = true)
+ .isVisible(ComponentNameMatcher.SPLASH_SCREEN, isOptional = true)
+ .then()
+ // App shows up with the custom animation starting at alpha=1.
+ .isVisible(testApp)
+ .then()
+ // App custom animation continues to alpha=0 (invisible).
+ .isInvisible(testApp)
+ .then()
+ // App custom animation ends with it being visible.
+ .isVisible(testApp)
}
}
@@ -123,8 +123,8 @@ class OverrideTaskTransitionTest(val testSpec: FlickerTestParameter) {
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests()
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests()
}
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
index 08624eed27a5..4e7ab7a24f65 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
@@ -22,13 +22,13 @@ import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Postsubmit
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.BaseTest
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.helpers.NewTasksAppHelper
import com.android.server.wm.flicker.helpers.SimpleAppHelper
import com.android.server.wm.flicker.helpers.WindowUtils
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.traces.common.ComponentNameMatcher
import com.android.server.wm.traces.common.ComponentNameMatcher.Companion.SPLASH_SCREEN
import com.android.server.wm.traces.common.ComponentNameMatcher.Companion.WALLPAPER_BBQ_WRAPPER
@@ -56,7 +56,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class TaskTransitionTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
+class TaskTransitionTest(flicker: FlickerTest) : BaseTest(flicker) {
private val testApp = NewTasksAppHelper(instrumentation)
private val simpleApp = SimpleAppHelper(instrumentation)
private val wallpaper by lazy {
@@ -81,7 +81,7 @@ class TaskTransitionTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
@FlakyTest(bugId = 253617416)
@Test
fun wallpaperWindowIsNeverVisible() {
- testSpec.assertWm { this.isNonAppWindowInvisible(wallpaper) }
+ flicker.assertWm { this.isNonAppWindowInvisible(wallpaper) }
}
/**
@@ -91,7 +91,7 @@ class TaskTransitionTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
@FlakyTest(bugId = 253617416)
@Test
fun wallpaperLayerIsNeverVisible() {
- testSpec.assertLayers {
+ flicker.assertLayers {
this.isInvisible(wallpaper)
this.isInvisible(WALLPAPER_BBQ_WRAPPER)
}
@@ -104,7 +104,7 @@ class TaskTransitionTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
@Postsubmit
@Test
fun launcherWindowIsNeverVisible() {
- testSpec.assertWm { this.isAppWindowInvisible(ComponentNameMatcher.LAUNCHER) }
+ flicker.assertWm { this.isAppWindowInvisible(ComponentNameMatcher.LAUNCHER) }
}
/**
@@ -114,7 +114,7 @@ class TaskTransitionTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
@Postsubmit
@Test
fun launcherLayerIsNeverVisible() {
- testSpec.assertLayers { this.isInvisible(ComponentNameMatcher.LAUNCHER) }
+ flicker.assertLayers { this.isInvisible(ComponentNameMatcher.LAUNCHER) }
}
/** Checks that a color background is visible while the task transition is occurring. */
@@ -122,9 +122,9 @@ class TaskTransitionTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
@Test
fun colorLayerIsVisibleDuringTransition() {
val bgColorLayer = ComponentNameMatcher("", "colorBackgroundLayer")
- val displayBounds = WindowUtils.getDisplayBounds(testSpec.startRotation)
+ val displayBounds = WindowUtils.getDisplayBounds(flicker.scenario.startRotation)
- testSpec.assertLayers {
+ flicker.assertLayers {
this.invoke("LAUNCH_NEW_TASK_ACTIVITY coversExactly displayBounds") {
it.visibleRegion(testApp.componentMatcher).coversExactly(displayBounds)
}
@@ -157,7 +157,7 @@ class TaskTransitionTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
@Postsubmit
@Test
fun newTaskOpensOnTopAndThenCloses() {
- testSpec.assertWm {
+ flicker.assertWm {
this.isAppWindowOnTop(testApp.componentMatcher)
.then()
.isAppWindowOnTop(SPLASH_SCREEN, isOptional = true)
@@ -235,8 +235,8 @@ class TaskTransitionTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests()
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests()
}
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
index bc1f0d18d8f4..b4a67eff75ee 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
@@ -18,18 +18,17 @@ package com.android.server.wm.flicker.quickswitch
import android.platform.test.annotations.Presubmit
import android.platform.test.annotations.RequiresDevice
-import android.view.Surface
-import android.view.WindowManagerPolicyConstants
import com.android.server.wm.flicker.BaseTest
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
import com.android.server.wm.flicker.helpers.SimpleAppHelper
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.traces.common.ComponentNameMatcher
import com.android.server.wm.traces.common.Rect
+import com.android.server.wm.traces.common.service.PlatformConsts
import org.junit.Assume
import org.junit.Before
import org.junit.FixMethodOrder
@@ -54,7 +53,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class QuickSwitchBetweenTwoAppsBackTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
+open class QuickSwitchBetweenTwoAppsBackTest(flicker: FlickerTest) : BaseTest(flicker) {
private val testApp1 = SimpleAppHelper(instrumentation)
private val testApp2 = NonResizeableAppHelper(instrumentation)
@@ -66,7 +65,7 @@ open class QuickSwitchBetweenTwoAppsBackTest(testSpec: FlickerTestParameter) : B
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit = {
setup {
- tapl.setExpectedRotation(testSpec.startRotation)
+ tapl.setExpectedRotation(flicker.scenario.startRotation.value)
tapl.setIgnoreTaskbarVisibility(true)
testApp1.launchViaIntent(wmHelper)
testApp2.launchViaIntent(wmHelper)
@@ -96,7 +95,7 @@ open class QuickSwitchBetweenTwoAppsBackTest(testSpec: FlickerTestParameter) : B
@Presubmit
@Test
open fun startsWithApp2WindowsCoverFullScreen() {
- testSpec.assertWmStart { this.visibleRegion(testApp2).coversExactly(startDisplayBounds) }
+ flicker.assertWmStart { this.visibleRegion(testApp2).coversExactly(startDisplayBounds) }
}
/**
@@ -106,16 +105,14 @@ open class QuickSwitchBetweenTwoAppsBackTest(testSpec: FlickerTestParameter) : B
@Presubmit
@Test
open fun startsWithApp2LayersCoverFullScreen() {
- testSpec.assertLayersStart {
- this.visibleRegion(testApp2).coversExactly(startDisplayBounds)
- }
+ flicker.assertLayersStart { this.visibleRegion(testApp2).coversExactly(startDisplayBounds) }
}
/** Checks that the transition starts with [testApp2] being the top window. */
@Presubmit
@Test
open fun startsWithApp2WindowBeingOnTop() {
- testSpec.assertWmStart { this.isAppWindowOnTop(testApp2) }
+ flicker.assertWmStart { this.isAppWindowOnTop(testApp2) }
}
/**
@@ -125,7 +122,7 @@ open class QuickSwitchBetweenTwoAppsBackTest(testSpec: FlickerTestParameter) : B
@Presubmit
@Test
open fun endsWithApp1WindowsCoveringFullScreen() {
- testSpec.assertWmEnd { this.visibleRegion(testApp1).coversExactly(startDisplayBounds) }
+ flicker.assertWmEnd { this.visibleRegion(testApp1).coversExactly(startDisplayBounds) }
}
/**
@@ -135,7 +132,7 @@ open class QuickSwitchBetweenTwoAppsBackTest(testSpec: FlickerTestParameter) : B
@Presubmit
@Test
fun endsWithApp1LayersCoveringFullScreen() {
- testSpec.assertLayersEnd { this.visibleRegion(testApp1).coversExactly(startDisplayBounds) }
+ flicker.assertLayersEnd { this.visibleRegion(testApp1).coversExactly(startDisplayBounds) }
}
/**
@@ -145,7 +142,7 @@ open class QuickSwitchBetweenTwoAppsBackTest(testSpec: FlickerTestParameter) : B
@Presubmit
@Test
open fun endsWithApp1BeingOnTop() {
- testSpec.assertWmEnd { this.isAppWindowOnTop(testApp1) }
+ flicker.assertWmEnd { this.isAppWindowOnTop(testApp1) }
}
/**
@@ -155,7 +152,7 @@ open class QuickSwitchBetweenTwoAppsBackTest(testSpec: FlickerTestParameter) : B
@Presubmit
@Test
open fun app1WindowBecomesAndStaysVisible() {
- testSpec.assertWm {
+ flicker.assertWm {
this.isAppWindowInvisible(testApp1)
.then()
.isAppWindowVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
@@ -171,7 +168,7 @@ open class QuickSwitchBetweenTwoAppsBackTest(testSpec: FlickerTestParameter) : B
@Presubmit
@Test
open fun app1LayerBecomesAndStaysVisible() {
- testSpec.assertLayers { this.isInvisible(testApp1).then().isVisible(testApp1) }
+ flicker.assertLayers { this.isInvisible(testApp1).then().isVisible(testApp1) }
}
/**
@@ -181,9 +178,7 @@ open class QuickSwitchBetweenTwoAppsBackTest(testSpec: FlickerTestParameter) : B
@Presubmit
@Test
open fun app2WindowBecomesAndStaysInvisible() {
- testSpec.assertWm {
- this.isAppWindowVisible(testApp2).then().isAppWindowInvisible(testApp2)
- }
+ flicker.assertWm { this.isAppWindowVisible(testApp2).then().isAppWindowInvisible(testApp2) }
}
/**
@@ -193,7 +188,7 @@ open class QuickSwitchBetweenTwoAppsBackTest(testSpec: FlickerTestParameter) : B
@Presubmit
@Test
open fun app2LayerBecomesAndStaysInvisible() {
- testSpec.assertLayers { this.isVisible(testApp2).then().isInvisible(testApp2) }
+ flicker.assertLayers { this.isVisible(testApp2).then().isInvisible(testApp2) }
}
/**
@@ -204,7 +199,7 @@ open class QuickSwitchBetweenTwoAppsBackTest(testSpec: FlickerTestParameter) : B
@Presubmit
@Test
open fun app1WindowIsVisibleOnceApp2WindowIsInvisible() {
- testSpec.assertWm {
+ flicker.assertWm {
this.isAppWindowVisible(testApp2)
.then()
// TODO: Do we actually want to test this? Seems too implementation specific...
@@ -224,7 +219,7 @@ open class QuickSwitchBetweenTwoAppsBackTest(testSpec: FlickerTestParameter) : B
@Presubmit
@Test
open fun app1LayerIsVisibleOnceApp2LayerIsInvisible() {
- testSpec.assertLayers {
+ flicker.assertLayers {
this.isVisible(testApp2)
.then()
.isVisible(ComponentNameMatcher.LAUNCHER, isOptional = true)
@@ -240,13 +235,10 @@ open class QuickSwitchBetweenTwoAppsBackTest(testSpec: FlickerTestParameter) : B
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(
- supportedNavigationModes =
- listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY),
- supportedRotations = listOf(Surface.ROTATION_0, Surface.ROTATION_90)
- )
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ supportedNavigationModes = listOf(PlatformConsts.NavBar.MODE_GESTURAL)
+ )
}
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest_ShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest_ShellTransit.kt
index f988bb2f1bf1..ec4e35c0f9dd 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest_ShellTransit.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest_ShellTransit.kt
@@ -19,9 +19,9 @@ package com.android.server.wm.flicker.quickswitch
import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
import android.platform.test.annotations.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.navBarWindowIsVisibleAtStartAndEnd
import com.android.server.wm.traces.common.ComponentNameMatcher
import org.junit.Assume
@@ -49,8 +49,8 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class QuickSwitchBetweenTwoAppsBackTest_ShellTransit(testSpec: FlickerTestParameter) :
- QuickSwitchBetweenTwoAppsBackTest(testSpec) {
+open class QuickSwitchBetweenTwoAppsBackTest_ShellTransit(flicker: FlickerTest) :
+ QuickSwitchBetweenTwoAppsBackTest(flicker) {
@Before
override fun before() {
Assume.assumeTrue(isShellTransitionsEnabled)
@@ -62,21 +62,20 @@ open class QuickSwitchBetweenTwoAppsBackTest_ShellTransit(testSpec: FlickerTestP
override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
/**
- * Checks that [ComponentNameMatcher.NAV_BAR] window is visible and above the app windows at
- * the start and end of the WM trace
+ * Checks that [ComponentNameMatcher.NAV_BAR] window is visible and above the app windows at the
+ * start and end of the WM trace
*/
@Presubmit
@Test
fun navBarWindowIsVisibleAtStartAndEnd() {
- Assume.assumeFalse(testSpec.isTablet)
- testSpec.navBarWindowIsVisibleAtStartAndEnd()
+ Assume.assumeFalse(flicker.scenario.isTablet)
+ flicker.navBarWindowIsVisibleAtStartAndEnd()
}
/** {@inheritDoc} */
@FlakyTest(bugId = 250520840)
@Test
- override fun startsWithApp2LayersCoverFullScreen() =
- super.startsWithApp2LayersCoverFullScreen()
+ override fun startsWithApp2LayersCoverFullScreen() = super.startsWithApp2LayersCoverFullScreen()
@FlakyTest(bugId = 246284708)
@Test
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
index 7e4504bc7a48..593481cfc221 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
@@ -18,18 +18,17 @@ package com.android.server.wm.flicker.quickswitch
import android.platform.test.annotations.Presubmit
import android.platform.test.annotations.RequiresDevice
-import android.view.Surface
-import android.view.WindowManagerPolicyConstants
import com.android.server.wm.flicker.BaseTest
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
import com.android.server.wm.flicker.helpers.SimpleAppHelper
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.traces.common.ComponentNameMatcher
import com.android.server.wm.traces.common.Rect
+import com.android.server.wm.traces.common.service.PlatformConsts
import org.junit.Assume
import org.junit.Before
import org.junit.FixMethodOrder
@@ -55,8 +54,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class QuickSwitchBetweenTwoAppsForwardTest(testSpec: FlickerTestParameter) :
- BaseTest(testSpec) {
+open class QuickSwitchBetweenTwoAppsForwardTest(flicker: FlickerTest) : BaseTest(flicker) {
private val testApp1 = SimpleAppHelper(instrumentation)
private val testApp2 = NonResizeableAppHelper(instrumentation)
@@ -68,7 +66,7 @@ open class QuickSwitchBetweenTwoAppsForwardTest(testSpec: FlickerTestParameter)
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit = {
setup {
- tapl.setExpectedRotation(testSpec.startRotation)
+ tapl.setExpectedRotation(flicker.scenario.startRotation.value)
testApp1.launchViaIntent(wmHelper)
testApp2.launchViaIntent(wmHelper)
@@ -105,7 +103,7 @@ open class QuickSwitchBetweenTwoAppsForwardTest(testSpec: FlickerTestParameter)
@Presubmit
@Test
open fun startsWithApp1WindowsCoverFullScreen() {
- testSpec.assertWmStart {
+ flicker.assertWmStart {
this.visibleRegion(testApp1.or(ComponentNameMatcher.LETTERBOX))
.coversExactly(startDisplayBounds)
}
@@ -118,16 +116,14 @@ open class QuickSwitchBetweenTwoAppsForwardTest(testSpec: FlickerTestParameter)
@Presubmit
@Test
open fun startsWithApp1LayersCoverFullScreen() {
- testSpec.assertLayersStart {
- this.visibleRegion(testApp1).coversExactly(startDisplayBounds)
- }
+ flicker.assertLayersStart { this.visibleRegion(testApp1).coversExactly(startDisplayBounds) }
}
/** Checks that the transition starts with [testApp1] being the top window. */
@Presubmit
@Test
open fun startsWithApp1WindowBeingOnTop() {
- testSpec.assertWmStart { this.isAppWindowOnTop(testApp1) }
+ flicker.assertWmStart { this.isAppWindowOnTop(testApp1) }
}
/**
@@ -137,7 +133,7 @@ open class QuickSwitchBetweenTwoAppsForwardTest(testSpec: FlickerTestParameter)
@Presubmit
@Test
open fun endsWithApp2WindowsCoveringFullScreen() {
- testSpec.assertWmEnd { this.visibleRegion(testApp2).coversExactly(startDisplayBounds) }
+ flicker.assertWmEnd { this.visibleRegion(testApp2).coversExactly(startDisplayBounds) }
}
/**
@@ -147,7 +143,7 @@ open class QuickSwitchBetweenTwoAppsForwardTest(testSpec: FlickerTestParameter)
@Presubmit
@Test
open fun endsWithApp2LayersCoveringFullScreen() {
- testSpec.assertLayersEnd {
+ flicker.assertLayersEnd {
this.visibleRegion(testApp2.or(ComponentNameMatcher.LETTERBOX))
.coversExactly(startDisplayBounds)
}
@@ -160,7 +156,7 @@ open class QuickSwitchBetweenTwoAppsForwardTest(testSpec: FlickerTestParameter)
@Presubmit
@Test
open fun endsWithApp2BeingOnTop() {
- testSpec.assertWmEnd { this.isAppWindowOnTop(testApp2) }
+ flicker.assertWmEnd { this.isAppWindowOnTop(testApp2) }
}
/**
@@ -170,7 +166,7 @@ open class QuickSwitchBetweenTwoAppsForwardTest(testSpec: FlickerTestParameter)
@Presubmit
@Test
open fun app2WindowBecomesAndStaysVisible() {
- testSpec.assertWm {
+ flicker.assertWm {
this.isAppWindowInvisible(testApp2)
.then()
.isAppWindowVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
@@ -186,7 +182,7 @@ open class QuickSwitchBetweenTwoAppsForwardTest(testSpec: FlickerTestParameter)
@Presubmit
@Test
open fun app2LayerBecomesAndStaysVisible() {
- testSpec.assertLayers { this.isInvisible(testApp2).then().isVisible(testApp2) }
+ flicker.assertLayers { this.isInvisible(testApp2).then().isVisible(testApp2) }
}
/**
@@ -196,9 +192,7 @@ open class QuickSwitchBetweenTwoAppsForwardTest(testSpec: FlickerTestParameter)
@Presubmit
@Test
open fun app1WindowBecomesAndStaysInvisible() {
- testSpec.assertWm {
- this.isAppWindowVisible(testApp1).then().isAppWindowInvisible(testApp1)
- }
+ flicker.assertWm { this.isAppWindowVisible(testApp1).then().isAppWindowInvisible(testApp1) }
}
/**
@@ -208,7 +202,7 @@ open class QuickSwitchBetweenTwoAppsForwardTest(testSpec: FlickerTestParameter)
@Presubmit
@Test
open fun app1LayerBecomesAndStaysInvisible() {
- testSpec.assertLayers { this.isVisible(testApp1).then().isInvisible(testApp1) }
+ flicker.assertLayers { this.isVisible(testApp1).then().isInvisible(testApp1) }
}
/**
@@ -219,7 +213,7 @@ open class QuickSwitchBetweenTwoAppsForwardTest(testSpec: FlickerTestParameter)
@Presubmit
@Test
open fun app2WindowIsVisibleOnceApp1WindowIsInvisible() {
- testSpec.assertWm {
+ flicker.assertWm {
this.isAppWindowVisible(testApp1)
.then()
.isAppWindowVisible(ComponentNameMatcher.LAUNCHER, isOptional = true)
@@ -238,7 +232,7 @@ open class QuickSwitchBetweenTwoAppsForwardTest(testSpec: FlickerTestParameter)
@Presubmit
@Test
open fun app2LayerIsVisibleOnceApp1LayerIsInvisible() {
- testSpec.assertLayers {
+ flicker.assertLayers {
this.isVisible(testApp1)
.then()
.isVisible(ComponentNameMatcher.LAUNCHER, isOptional = true)
@@ -259,13 +253,10 @@ open class QuickSwitchBetweenTwoAppsForwardTest(testSpec: FlickerTestParameter)
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(
- supportedNavigationModes =
- listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY),
- supportedRotations = listOf(Surface.ROTATION_0, Surface.ROTATION_90)
- )
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ supportedNavigationModes = listOf(PlatformConsts.NavBar.MODE_GESTURAL)
+ )
}
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest_ShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest_ShellTransit.kt
index cc954ab6ee5d..477b41973454 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest_ShellTransit.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest_ShellTransit.kt
@@ -19,9 +19,9 @@ package com.android.server.wm.flicker.quickswitch
import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
import android.platform.test.annotations.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.navBarWindowIsVisibleAtStartAndEnd
import com.android.server.wm.traces.common.ComponentNameMatcher
import org.junit.Assume
@@ -50,8 +50,8 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class QuickSwitchBetweenTwoAppsForwardTest_ShellTransit(testSpec: FlickerTestParameter) :
- QuickSwitchBetweenTwoAppsForwardTest(testSpec) {
+open class QuickSwitchBetweenTwoAppsForwardTest_ShellTransit(flicker: FlickerTest) :
+ QuickSwitchBetweenTwoAppsForwardTest(flicker) {
@Before
override fun before() {
Assume.assumeTrue(isShellTransitionsEnabled)
@@ -63,14 +63,14 @@ open class QuickSwitchBetweenTwoAppsForwardTest_ShellTransit(testSpec: FlickerTe
override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
/**
- * Checks that [ComponentNameMatcher.NAV_BAR] window is visible and above the app windows at
- * the start and end of the WM trace
+ * Checks that [ComponentNameMatcher.NAV_BAR] window is visible and above the app windows at the
+ * start and end of the WM trace
*/
@Presubmit
@Test
fun navBarWindowIsVisibleAtStartAndEnd() {
- Assume.assumeFalse(testSpec.isTablet)
- testSpec.navBarWindowIsVisibleAtStartAndEnd()
+ Assume.assumeFalse(flicker.scenario.isTablet)
+ flicker.navBarWindowIsVisibleAtStartAndEnd()
}
@FlakyTest(bugId = 246284708)
@@ -84,6 +84,5 @@ open class QuickSwitchBetweenTwoAppsForwardTest_ShellTransit(testSpec: FlickerTe
@FlakyTest(bugId = 250522691)
@Test
- override fun startsWithApp1LayersCoverFullScreen() =
- super.startsWithApp1LayersCoverFullScreen()
+ override fun startsWithApp1LayersCoverFullScreen() = super.startsWithApp1LayersCoverFullScreen()
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
index 3cb985a925b1..8c8220ff04c9 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
@@ -19,18 +19,17 @@ package com.android.server.wm.flicker.quickswitch
import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
import android.platform.test.annotations.RequiresDevice
-import android.view.Surface
-import android.view.WindowManagerPolicyConstants
import com.android.server.wm.flicker.BaseTest
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.helpers.SimpleAppHelper
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.navBarWindowIsVisibleAtStartAndEnd
import com.android.server.wm.traces.common.ComponentNameMatcher
import com.android.server.wm.traces.common.Rect
+import com.android.server.wm.traces.common.service.PlatformConsts
import org.junit.Assume
import org.junit.FixMethodOrder
import org.junit.Ignore
@@ -55,7 +54,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class QuickSwitchFromLauncherTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
+class QuickSwitchFromLauncherTest(flicker: FlickerTest) : BaseTest(flicker) {
private val testApp = SimpleAppHelper(instrumentation)
/** {@inheritDoc} */
@@ -63,7 +62,7 @@ class QuickSwitchFromLauncherTest(testSpec: FlickerTestParameter) : BaseTest(tes
setup {
tapl.setExpectedRotationCheckEnabled(false)
- tapl.setExpectedRotation(testSpec.startRotation)
+ tapl.setExpectedRotation(flicker.scenario.startRotation.value)
testApp.launchViaIntent(wmHelper)
tapl.goHome()
@@ -95,7 +94,7 @@ class QuickSwitchFromLauncherTest(testSpec: FlickerTestParameter) : BaseTest(tes
@Presubmit
@Test
fun endsWithAppWindowsCoveringFullScreen() {
- testSpec.assertWmEnd { this.visibleRegion(testApp).coversExactly(startDisplayBounds) }
+ flicker.assertWmEnd { this.visibleRegion(testApp).coversExactly(startDisplayBounds) }
}
/**
@@ -105,7 +104,7 @@ class QuickSwitchFromLauncherTest(testSpec: FlickerTestParameter) : BaseTest(tes
@Presubmit
@Test
fun endsWithAppLayersCoveringFullScreen() {
- testSpec.assertLayersEnd { this.visibleRegion(testApp).coversExactly(startDisplayBounds) }
+ flicker.assertLayersEnd { this.visibleRegion(testApp).coversExactly(startDisplayBounds) }
}
/**
@@ -115,47 +114,48 @@ class QuickSwitchFromLauncherTest(testSpec: FlickerTestParameter) : BaseTest(tes
@Presubmit
@Test
fun endsWithAppBeingOnTop() {
- testSpec.assertWmEnd { this.isAppWindowOnTop(testApp) }
+ flicker.assertWmEnd { this.isAppWindowOnTop(testApp) }
}
/** Checks that the transition starts with the home activity being tagged as visible. */
@Presubmit
@Test
fun startsWithHomeActivityFlaggedVisible() {
- testSpec.assertWmStart { this.isHomeActivityVisible() }
+ flicker.assertWmStart { this.isHomeActivityVisible() }
}
/**
- * Checks that the transition starts with the [ComponentMatcher.LAUNCHER] windows
+ * Checks that the transition starts with the [ComponentNameMatcher.LAUNCHER] windows
* filling/covering exactly display size
*/
@Presubmit
@Test
fun startsWithLauncherWindowsCoverFullScreen() {
- testSpec.assertWmStart {
+ flicker.assertWmStart {
this.visibleRegion(ComponentNameMatcher.LAUNCHER).coversExactly(startDisplayBounds)
}
}
/**
- * Checks that the transition starts with the [ComponentMatcher.LAUNCHER] layers
+ * Checks that the transition starts with the [ComponentNameMatcher.LAUNCHER] layers
* filling/covering exactly the display size.
*/
@Presubmit
@Test
fun startsWithLauncherLayersCoverFullScreen() {
- testSpec.assertLayersStart {
+ flicker.assertLayersStart {
this.visibleRegion(ComponentNameMatcher.LAUNCHER).coversExactly(startDisplayBounds)
}
}
/**
- * Checks that the transition starts with the [ComponentMatcher.LAUNCHER] being the top window.
+ * Checks that the transition starts with the [ComponentNameMatcher.LAUNCHER] being the top
+ * window.
*/
@Presubmit
@Test
fun startsWithLauncherBeingOnTop() {
- testSpec.assertWmStart { this.isAppWindowOnTop(ComponentNameMatcher.LAUNCHER) }
+ flicker.assertWmStart { this.isAppWindowOnTop(ComponentNameMatcher.LAUNCHER) }
}
/**
@@ -165,7 +165,7 @@ class QuickSwitchFromLauncherTest(testSpec: FlickerTestParameter) : BaseTest(tes
@Presubmit
@Test
fun endsWithHomeActivityFlaggedInvisible() {
- testSpec.assertWmEnd { this.isHomeActivityInvisible() }
+ flicker.assertWmEnd { this.isHomeActivityInvisible() }
}
/**
@@ -175,7 +175,7 @@ class QuickSwitchFromLauncherTest(testSpec: FlickerTestParameter) : BaseTest(tes
@Presubmit
@Test
fun appWindowBecomesAndStaysVisible() {
- testSpec.assertWm { this.isAppWindowInvisible(testApp).then().isAppWindowVisible(testApp) }
+ flicker.assertWm { this.isAppWindowInvisible(testApp).then().isAppWindowVisible(testApp) }
}
/**
@@ -185,18 +185,18 @@ class QuickSwitchFromLauncherTest(testSpec: FlickerTestParameter) : BaseTest(tes
@Presubmit
@Test
fun appLayerBecomesAndStaysVisible() {
- testSpec.assertLayers { this.isInvisible(testApp).then().isVisible(testApp) }
+ flicker.assertLayers { this.isInvisible(testApp).then().isVisible(testApp) }
}
/**
- * Checks that the [ComponentMatcher.LAUNCHER] window starts off visible and becomes invisible
- * at some point before the end of the transition and then stays invisible until the end of the
- * transition.
+ * Checks that the [ComponentNameMatcher.LAUNCHER] window starts off visible and becomes
+ * invisible at some point before the end of the transition and then stays invisible until the
+ * end of the transition.
*/
@Presubmit
@Test
fun launcherWindowBecomesAndStaysInvisible() {
- testSpec.assertWm {
+ flicker.assertWm {
this.isAppWindowOnTop(ComponentNameMatcher.LAUNCHER)
.then()
.isAppWindowNotOnTop(ComponentNameMatcher.LAUNCHER)
@@ -204,14 +204,14 @@ class QuickSwitchFromLauncherTest(testSpec: FlickerTestParameter) : BaseTest(tes
}
/**
- * Checks that the [ComponentMatcher.LAUNCHER] layer starts off visible and becomes invisible at
- * some point before the end of the transition and then stays invisible until the end of the
- * transition.
+ * Checks that the [ComponentNameMatcher.LAUNCHER] layer starts off visible and becomes
+ * invisible at some point before the end of the transition and then stays invisible until the
+ * end of the transition.
*/
@Presubmit
@Test
fun launcherLayerBecomesAndStaysInvisible() {
- testSpec.assertLayers {
+ flicker.assertLayers {
this.isVisible(ComponentNameMatcher.LAUNCHER)
.then()
.isInvisible(ComponentNameMatcher.LAUNCHER)
@@ -219,14 +219,14 @@ class QuickSwitchFromLauncherTest(testSpec: FlickerTestParameter) : BaseTest(tes
}
/**
- * Checks that the [ComponentMatcher.LAUNCHER] window is visible at least until the app window
- * is visible. Ensures that at any point, either the launcher or [testApp] windows are at least
- * partially visible.
+ * Checks that the [ComponentNameMatcher.LAUNCHER] window is visible at least until the app
+ * window is visible. Ensures that at any point, either the launcher or [testApp] windows are at
+ * least partially visible.
*/
@Presubmit
@Test
fun appWindowIsVisibleOnceLauncherWindowIsInvisible() {
- testSpec.assertWm {
+ flicker.assertWm {
this.isAppWindowOnTop(ComponentNameMatcher.LAUNCHER)
.then()
.isAppWindowVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
@@ -236,14 +236,14 @@ class QuickSwitchFromLauncherTest(testSpec: FlickerTestParameter) : BaseTest(tes
}
/**
- * Checks that the [ComponentMatcher.LAUNCHER] layer is visible at least until the app layer is
- * visible. Ensures that at any point, either the launcher or [testApp] layers are at least
+ * Checks that the [ComponentNameMatcher.LAUNCHER] layer is visible at least until the app layer
+ * is visible. Ensures that at any point, either the launcher or [testApp] layers are at least
* partially visible.
*/
@Presubmit
@Test
fun appLayerIsVisibleOnceLauncherLayerIsInvisible() {
- testSpec.assertLayers {
+ flicker.assertLayers {
this.isVisible(ComponentNameMatcher.LAUNCHER)
.then()
.isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
@@ -263,14 +263,14 @@ class QuickSwitchFromLauncherTest(testSpec: FlickerTestParameter) : BaseTest(tes
override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
/**
- * Checks that [ComponentMatcher.NAV_BAR] window is visible and above the app windows at the
+ * Checks that [ComponentNameMatcher.NAV_BAR] window is visible and above the app windows at the
* start and end of the WM trace
*/
@Presubmit
@Test
fun navBarWindowIsVisibleAtStartAndEnd() {
- Assume.assumeFalse(testSpec.isTablet)
- testSpec.navBarWindowIsVisibleAtStartAndEnd()
+ Assume.assumeFalse(flicker.scenario.isTablet)
+ flicker.navBarWindowIsVisibleAtStartAndEnd()
}
@Presubmit
@@ -293,14 +293,12 @@ class QuickSwitchFromLauncherTest(testSpec: FlickerTestParameter) : BaseTest(tes
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(
- supportedNavigationModes =
- listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY),
- // TODO: Test with 90 rotation
- supportedRotations = listOf(Surface.ROTATION_0)
- )
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ supportedNavigationModes = listOf(PlatformConsts.NavBar.MODE_GESTURAL),
+ // TODO: Test with 90 rotation
+ supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
+ )
}
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
index ad14d0d2b612..5b52c753d5f2 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
@@ -20,11 +20,11 @@ import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.IwTest
import android.platform.test.annotations.Presubmit
import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.traces.common.ComponentNameMatcher
import org.junit.FixMethodOrder
import org.junit.Test
@@ -78,7 +78,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ChangeAppRotationTest(testSpec: FlickerTestParameter) : RotationTransition(testSpec) {
+class ChangeAppRotationTest(flicker: FlickerTest) : RotationTransition(flicker) {
override val testApp = SimpleAppHelper(instrumentation)
override val transition: FlickerBuilder.() -> Unit
get() = {
@@ -93,15 +93,15 @@ class ChangeAppRotationTest(testSpec: FlickerTestParameter) : RotationTransition
@Presubmit
@Test
fun focusChanges() {
- testSpec.assertEventLog { this.focusChanges(testApp.`package`) }
+ flicker.assertEventLog { this.focusChanges(testApp.`package`) }
}
/**
- * Checks that the [ComponentMatcher.ROTATION] layer appears during the transition, doesn't
+ * Checks that the [ComponentNameMatcher.ROTATION] layer appears during the transition, doesn't
* flicker, and disappears before the transition is complete
*/
fun rotationLayerAppearsAndVanishesAssertion() {
- testSpec.assertLayers {
+ flicker.assertLayers {
this.isVisible(testApp)
.then()
.isVisible(ComponentNameMatcher.ROTATION)
@@ -112,7 +112,7 @@ class ChangeAppRotationTest(testSpec: FlickerTestParameter) : RotationTransition
}
/**
- * Checks that the [ComponentMatcher.ROTATION] layer appears during the transition, doesn't
+ * Checks that the [ComponentNameMatcher.ROTATION] layer appears during the transition, doesn't
* flicker, and disappears before the transition is complete
*/
@Presubmit
@@ -138,13 +138,13 @@ class ChangeAppRotationTest(testSpec: FlickerTestParameter) : RotationTransition
/**
* Creates the test configurations.
*
- * See [FlickerTestParameterFactory.getConfigRotationTests] for configuring repetitions,
- * screen orientation and navigation modes.
+ * See [FlickerTestFactory.rotationTests] for configuring screen orientation and navigation
+ * modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigRotationTests()
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.rotationTests()
}
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt
index 8e3fd4066000..4ef9eaf7b2e9 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt
@@ -18,29 +18,29 @@ package com.android.server.wm.flicker.rotation
import android.platform.test.annotations.Presubmit
import com.android.server.wm.flicker.BaseTest
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.helpers.StandardAppHelper
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.traces.common.ComponentNameMatcher
import org.junit.Test
/** Base class for app rotation tests */
-abstract class RotationTransition(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
+abstract class RotationTransition(flicker: FlickerTest) : BaseTest(flicker) {
protected abstract val testApp: StandardAppHelper
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit = {
- setup { this.setRotation(testSpec.startRotation) }
+ setup { this.setRotation(flicker.scenario.startRotation) }
teardown { testApp.exit(wmHelper) }
- transitions { this.setRotation(testSpec.endRotation) }
+ transitions { this.setRotation(flicker.scenario.endRotation) }
}
/** {@inheritDoc} */
@Presubmit
@Test
override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
- testSpec.assertLayers {
+ flicker.assertLayers {
this.visibleLayersShownMoreThanOneConsecutiveEntry(
ignoreLayers =
listOf(
@@ -56,7 +56,7 @@ abstract class RotationTransition(testSpec: FlickerTestParameter) : BaseTest(tes
@Presubmit
@Test
open fun appLayerRotates_StartingPos() {
- testSpec.assertLayersStart {
+ flicker.assertLayersStart {
this.entry.displays.map { display ->
this.visibleRegion(testApp).coversExactly(display.layerStackSpace)
}
@@ -67,7 +67,7 @@ abstract class RotationTransition(testSpec: FlickerTestParameter) : BaseTest(tes
@Presubmit
@Test
open fun appLayerRotates_EndingPos() {
- testSpec.assertLayersEnd {
+ flicker.assertLayersEnd {
this.entry.displays.map { display ->
this.visibleRegion(testApp).coversExactly(display.layerStackSpace)
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
index d0d4122423fe..54f38c32f445 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
@@ -21,11 +21,12 @@ import android.platform.test.annotations.IwTest
import android.platform.test.annotations.Presubmit
import android.platform.test.annotations.RequiresDevice
import android.view.WindowManager
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
+import com.android.server.wm.flicker.ScenarioBuilder
import com.android.server.wm.flicker.helpers.SeamlessRotationAppHelper
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.testapp.ActivityOptions
import com.android.server.wm.traces.common.ComponentNameMatcher
import org.junit.FixMethodOrder
@@ -38,7 +39,7 @@ import org.junit.runners.Parameterized
/**
* Test opening an app and cycling through app rotations using seamless rotations
*
- * Currently runs:
+ * Currently, runs:
* ```
* 0 -> 90 degrees
* 0 -> 90 degrees (with starved UI thread)
@@ -83,7 +84,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class SeamlessAppRotationTest(testSpec: FlickerTestParameter) : RotationTransition(testSpec) {
+open class SeamlessAppRotationTest(flicker: FlickerTest) : RotationTransition(flicker) {
override val testApp = SeamlessRotationAppHelper(instrumentation)
/** {@inheritDoc} */
@@ -96,7 +97,7 @@ open class SeamlessAppRotationTest(testSpec: FlickerTestParameter) : RotationTra
stringExtras =
mapOf(
ActivityOptions.SeamlessRotation.EXTRA_STARVE_UI_THREAD to
- testSpec.starveUiThread.toString()
+ flicker.starveUiThread.toString()
)
)
}
@@ -106,7 +107,7 @@ open class SeamlessAppRotationTest(testSpec: FlickerTestParameter) : RotationTra
@Presubmit
@Test
fun appWindowFullScreen() {
- testSpec.assertWm {
+ flicker.assertWm {
this.invoke("isFullScreen") {
val appWindow = it.windowState(testApp.`package`)
val flags = appWindow.windowState?.attributes?.flags ?: 0
@@ -122,7 +123,7 @@ open class SeamlessAppRotationTest(testSpec: FlickerTestParameter) : RotationTra
@Presubmit
@Test
fun appWindowSeamlessRotation() {
- testSpec.assertWm {
+ flicker.assertWm {
this.invoke("isRotationSeamless") {
val appWindow = it.windowState(testApp.`package`)
val rotationAnimation = appWindow.windowState?.attributes?.rotationAnimation ?: 0
@@ -142,14 +143,14 @@ open class SeamlessAppRotationTest(testSpec: FlickerTestParameter) : RotationTra
@Presubmit
@Test
fun appLayerAlwaysVisible() {
- testSpec.assertLayers { isVisible(testApp) }
+ flicker.assertLayers { isVisible(testApp) }
}
/** Checks that [testApp] layer covers the entire screen during the whole transition */
@Presubmit
@Test
fun appLayerRotates() {
- testSpec.assertLayers {
+ flicker.assertLayers {
this.invoke("entireScreenCovered") { entry ->
entry.entry.displays.map { display ->
entry.visibleRegion(testApp).coversExactly(display.layerStackSpace)
@@ -180,7 +181,7 @@ open class SeamlessAppRotationTest(testSpec: FlickerTestParameter) : RotationTra
@Presubmit
@Test
fun statusBarWindowIsAlwaysInvisible() {
- testSpec.assertWm { this.isAboveAppWindowInvisible(ComponentNameMatcher.STATUS_BAR) }
+ flicker.assertWm { this.isAboveAppWindowInvisible(ComponentNameMatcher.STATUS_BAR) }
}
/**
@@ -190,14 +191,14 @@ open class SeamlessAppRotationTest(testSpec: FlickerTestParameter) : RotationTra
@Presubmit
@Test
fun statusBarLayerIsAlwaysInvisible() {
- testSpec.assertLayers { this.isInvisible(ComponentNameMatcher.STATUS_BAR) }
+ flicker.assertLayers { this.isInvisible(ComponentNameMatcher.STATUS_BAR) }
}
/** Checks that the focus doesn't change during animation */
@Presubmit
@Test
fun focusDoesNotChange() {
- testSpec.assertEventLog { this.focusDoesNotChange() }
+ flicker.assertEventLog { this.focusDoesNotChange() }
}
/** {@inheritDoc} */
@@ -208,7 +209,7 @@ open class SeamlessAppRotationTest(testSpec: FlickerTestParameter) : RotationTra
@Test
@IwTest(focusArea = "ime")
override fun cujCompleted() {
- if (!testSpec.isTablet) {
+ if (!flicker.scenario.isTablet) {
// not yet tablet compatible
appLayerRotates()
appLayerAlwaysVisible()
@@ -231,49 +232,39 @@ open class SeamlessAppRotationTest(testSpec: FlickerTestParameter) : RotationTra
}
companion object {
- private val FlickerTestParameter.starveUiThread
+ private val FlickerTest.starveUiThread
get() =
- config.getOrDefault(ActivityOptions.SeamlessRotation.EXTRA_STARVE_UI_THREAD, false)
- as Boolean
+ getConfigValue<Boolean>(ActivityOptions.SeamlessRotation.EXTRA_STARVE_UI_THREAD)
+ ?: false
- private fun createConfig(
- sourceConfig: FlickerTestParameter,
- starveUiThread: Boolean
- ): FlickerTestParameter {
- val newConfig =
- sourceConfig.config.toMutableMap().also {
- it[ActivityOptions.SeamlessRotation.EXTRA_STARVE_UI_THREAD] = starveUiThread
- }
+ private fun createConfig(sourceConfig: FlickerTest, starveUiThread: Boolean): FlickerTest {
+ val originalScenario = sourceConfig.initialize("createConfig")
val nameExt = if (starveUiThread) "_BUSY_UI_THREAD" else ""
- return FlickerTestParameter(newConfig, nameOverride = "$sourceConfig$nameExt")
+ val newConfig =
+ ScenarioBuilder()
+ .fromScenario(originalScenario)
+ .withExtraConfig(
+ ActivityOptions.SeamlessRotation.EXTRA_STARVE_UI_THREAD,
+ starveUiThread
+ )
+ .withDescriptionOverride("${originalScenario.description}$nameExt")
+ return FlickerTest(newConfig)
}
/**
* Creates the test configurations for seamless rotation based on the default rotation tests
- * from [FlickerTestParameterFactory.getConfigRotationTests], but adding an additional flag
- * ([ActivityOptions.SeamlessRotation.EXTRA_STARVE_UI_THREAD]) to indicate if the app should
+ * from [FlickerTestFactory.rotationTests], but adding a flag (
+ * [ActivityOptions.SeamlessRotation.EXTRA_STARVE_UI_THREAD]) to indicate if the app should
* starve the UI thread of not
- */
+ */
+ @Parameterized.Parameters(name = "{0}")
@JvmStatic
- private fun getConfigurations(): List<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigRotationTests().flatMap {
- sourceConfig ->
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.rotationTests().flatMap { sourceConfig ->
val defaultRun = createConfig(sourceConfig, starveUiThread = false)
val busyUiRun = createConfig(sourceConfig, starveUiThread = true)
listOf(defaultRun, busyUiRun)
}
}
-
- /**
- * Creates the test configurations.
- *
- * See [FlickerTestParameterFactory.getConfigRotationTests] for configuring repetitions,
- * screen orientation and navigation modes.
- */
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return getConfigurations()
- }
}
}
diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml
index 939c7de22356..7383d6ab4994 100644
--- a/tests/HwAccelerationTest/AndroidManifest.xml
+++ b/tests/HwAccelerationTest/AndroidManifest.xml
@@ -1146,5 +1146,13 @@
</intent-filter>
</activity>
+ <activity android:name="MeshActivity"
+ android:label="Mesh/SimpleMesh"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="com.android.test.hwui.TEST"/>
+ </intent-filter>
+ </activity>
</application>
</manifest>
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/MeshActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/MeshActivity.java
new file mode 100644
index 000000000000..efe242ce728e
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/MeshActivity.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.test.hwui;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.BlendMode;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Mesh;
+import android.graphics.MeshSpecification;
+import android.graphics.MeshSpecification.Attribute;
+import android.graphics.MeshSpecification.Varying;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.view.View;
+
+import java.nio.FloatBuffer;
+import java.nio.ShortBuffer;
+
+public class MeshActivity extends Activity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(new MeshView(this));
+ }
+
+ static class MeshView extends View {
+ MeshView(Context c) {
+ super(c);
+ this.setOnTouchListener((v, event) -> {
+ invalidate();
+ return true;
+ });
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+
+ MeshSpecification meshSpec = createMeshSpecification();
+ FloatBuffer vertexBuffer = FloatBuffer.allocate(6);
+ vertexBuffer.put(0, 100.0f);
+ vertexBuffer.put(1, 100.0f);
+ vertexBuffer.put(2, 400.0f);
+ vertexBuffer.put(3, 0.0f);
+ vertexBuffer.put(4, 0.0f);
+ vertexBuffer.put(5, 400.0f);
+ vertexBuffer.rewind();
+ Mesh mesh = Mesh.make(
+ meshSpec, Mesh.Mode.Triangles, vertexBuffer, 3, new Rect(0, 0, 1000, 1000));
+
+ int numTriangles = 100;
+ // number of triangles plus first 2 vertices
+ FloatBuffer iVertexBuffer = FloatBuffer.allocate(numTriangles * 2 + 4);
+ ShortBuffer indexBuffer = ShortBuffer.allocate(300);
+
+ int radius = 200;
+ // origin
+ iVertexBuffer.put(0, 500.0f);
+ iVertexBuffer.put(1, 500.0f);
+
+ // first point
+ iVertexBuffer.put(2, 500.0f + radius);
+ iVertexBuffer.put(3, 500.0f);
+ int nVert = 2;
+ int nInd = 0;
+ for (int i = 1; i <= numTriangles; i++) {
+ double angle = (Math.PI * i) / numTriangles;
+ double x = radius * Math.cos(angle);
+ double y = radius * Math.sin(angle);
+ iVertexBuffer.put((i + 1) * 2, 500 + (float) x);
+ iVertexBuffer.put((i + 1) * 2 + 1, 500 + (float) y);
+
+ indexBuffer.put(nInd++, (short) 0);
+ indexBuffer.put(nInd++, (short) (nVert - 1));
+ indexBuffer.put(nInd++, (short) nVert);
+ nVert++;
+ }
+ iVertexBuffer.rewind();
+ indexBuffer.rewind();
+ Mesh mesh2 = Mesh.makeIndexed(meshSpec, Mesh.Mode.Triangles, iVertexBuffer, 102,
+ indexBuffer, new Rect(0, 0, 1000, 1000));
+
+ Paint paint = new Paint();
+ paint.setColor(Color.RED);
+ canvas.drawMesh(mesh, BlendMode.COLOR, new Paint());
+ canvas.drawMesh(mesh2, BlendMode.COLOR, paint);
+ }
+
+ private MeshSpecification createMeshSpecification() {
+ String vs = "Varyings main(const Attributes attributes) { "
+ + " Varyings varyings;"
+ + " varyings.position = attributes.position;"
+ + " return varyings;"
+ + "}";
+ String fs = "float2 main(const Varyings varyings, out float4 color) {\n"
+ + " color = vec4(1.0, 0.0, 0.0, 1.0);"
+ + " return varyings.position;\n"
+ + "}";
+ Attribute[] attList =
+ new Attribute[] {new Attribute(MeshSpecification.FLOAT2, 0, "position")};
+ Varying[] varyList =
+ new MeshSpecification.Varying[] {};
+ return MeshSpecification.make(attList, 8, varyList, vs, fs);
+ }
+ }
+}
diff --git a/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/ControllerActivity.java b/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/ControllerActivity.java
index e6b60cfbe84f..167d560633ab 100644
--- a/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/ControllerActivity.java
+++ b/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/ControllerActivity.java
@@ -88,46 +88,7 @@ public class ControllerActivity extends Activity implements View.OnApplyWindowIn
}
private static String insetsTypesToString(int types) {
- if (types == 0) {
- return "none";
- }
- final StringBuilder sb = new StringBuilder();
- if ((types & Type.statusBars()) != 0) {
- types &= ~Type.statusBars();
- sb.append("statusBars ");
- }
- if ((types & Type.navigationBars()) != 0) {
- types &= ~Type.navigationBars();
- sb.append("navigationBars ");
- }
- if ((types & Type.captionBar()) != 0) {
- types &= ~Type.captionBar();
- sb.append("captionBar ");
- }
- if ((types & Type.ime()) != 0) {
- types &= ~Type.ime();
- sb.append("ime ");
- }
- if ((types & Type.systemGestures()) != 0) {
- types &= ~Type.systemGestures();
- sb.append("systemGestures ");
- }
- if ((types & Type.mandatorySystemGestures()) != 0) {
- types &= ~Type.mandatorySystemGestures();
- sb.append("mandatorySystemGestures ");
- }
- if ((types & Type.tappableElement()) != 0) {
- types &= ~Type.tappableElement();
- sb.append("tappableElement ");
- }
- if ((types & Type.displayCutout()) != 0) {
- types &= ~Type.displayCutout();
- sb.append("displayCutout ");
- }
- if (types != 0) {
- sb.append("unknownTypes:").append(types);
- }
- return sb.toString();
+ return types == 0 ? "none" : WindowInsets.Type.toString(types);
}
@Override
diff --git a/tests/utils/testutils/java/com/android/internal/util/test/BroadcastInterceptingContext.java b/tests/utils/testutils/java/com/android/internal/util/test/BroadcastInterceptingContext.java
index 133c1767c9b4..cc3781a0bfb3 100644
--- a/tests/utils/testutils/java/com/android/internal/util/test/BroadcastInterceptingContext.java
+++ b/tests/utils/testutils/java/com/android/internal/util/test/BroadcastInterceptingContext.java
@@ -246,6 +246,12 @@ public class BroadcastInterceptingContext extends ContextWrapper {
}
@Override
+ public void sendBroadcastAsUser(Intent intent, UserHandle user,
+ String receiverPermission, Bundle options) {
+ sendBroadcast(intent);
+ }
+
+ @Override
public void sendStickyBroadcast(Intent intent) {
sendBroadcast(intent);
}
diff --git a/tools/lint/common/src/main/java/com/google/android/lint/PermissionMethodUtils.kt b/tools/lint/common/src/main/java/com/google/android/lint/PermissionMethodUtils.kt
index 720f8356f050..01575963951b 100644
--- a/tools/lint/common/src/main/java/com/google/android/lint/PermissionMethodUtils.kt
+++ b/tools/lint/common/src/main/java/com/google/android/lint/PermissionMethodUtils.kt
@@ -17,6 +17,7 @@
package com.google.android.lint
import com.android.tools.lint.detector.api.getUMethod
+import org.jetbrains.uast.UAnnotation
import org.jetbrains.uast.UCallExpression
import org.jetbrains.uast.UMethod
import org.jetbrains.uast.UParameter
@@ -26,10 +27,11 @@ fun isPermissionMethodCall(callExpression: UCallExpression): Boolean {
return hasPermissionMethodAnnotation(method)
}
-fun hasPermissionMethodAnnotation(method: UMethod): Boolean = method.annotations
- .any {
- it.hasQualifiedName(ANNOTATION_PERMISSION_METHOD)
- }
+fun hasPermissionMethodAnnotation(method: UMethod): Boolean =
+ getPermissionMethodAnnotation(method) != null
+
+fun getPermissionMethodAnnotation(method: UMethod?): UAnnotation? = method?.uAnnotations
+ ?.firstOrNull { it.qualifiedName == ANNOTATION_PERMISSION_METHOD }
fun hasPermissionNameAnnotation(parameter: UParameter) = parameter.annotations.any {
it.hasQualifiedName(ANNOTATION_PERMISSION_NAME)
diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt
index 5c177d59e349..ee7dd62aaa36 100644
--- a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt
+++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt
@@ -19,9 +19,12 @@ package com.google.android.lint.aidl
import com.android.tools.lint.detector.api.JavaContext
import com.android.tools.lint.detector.api.LintFix
import com.android.tools.lint.detector.api.Location
+import com.android.tools.lint.detector.api.UastLintUtils.Companion.getAnnotationBooleanValue
import com.android.tools.lint.detector.api.getUMethod
+import com.google.android.lint.getPermissionMethodAnnotation
import com.google.android.lint.hasPermissionNameAnnotation
import com.google.android.lint.isPermissionMethodCall
+import com.intellij.psi.PsiType
import org.jetbrains.kotlin.psi.psiUtil.parameterIndex
import org.jetbrains.uast.UCallExpression
import org.jetbrains.uast.evaluateString
@@ -37,7 +40,8 @@ import org.jetbrains.uast.visitor.AbstractUastVisitor
*/
data class EnforcePermissionFix(
val locations: List<Location>,
- val permissionNames: List<String>
+ val permissionNames: List<String>,
+ val errorLevel: Boolean,
) {
fun toLintFix(annotationLocation: Location): LintFix {
val removeFixes = this.locations.map {
@@ -78,19 +82,28 @@ data class EnforcePermissionFix(
fun fromCallExpression(
context: JavaContext,
callExpression: UCallExpression
- ): EnforcePermissionFix? =
- if (isPermissionMethodCall(callExpression)) {
- EnforcePermissionFix(
+ ): EnforcePermissionFix? {
+ val method = callExpression.resolve()?.getUMethod() ?: return null
+ val annotation = getPermissionMethodAnnotation(method) ?: return null
+ val enforces = method.returnType == PsiType.VOID
+ val orSelf = getAnnotationBooleanValue(annotation, "orSelf") ?: false
+ return EnforcePermissionFix(
listOf(getPermissionCheckLocation(context, callExpression)),
- getPermissionCheckValues(callExpression)
- )
- } else null
+ getPermissionCheckValues(callExpression),
+ // If we detect that the PermissionMethod enforces that permission is granted,
+ // AND is of the "orSelf" variety, we are very confident that this is a behavior
+ // preserving migration to @EnforcePermission. Thus, the incident should be ERROR
+ // level.
+ errorLevel = enforces && orSelf
+ )
+ }
fun compose(individuals: List<EnforcePermissionFix>): EnforcePermissionFix =
EnforcePermissionFix(
individuals.flatMap { it.locations },
- individuals.flatMap { it.permissionNames }
+ individuals.flatMap { it.permissionNames },
+ errorLevel = individuals.all(EnforcePermissionFix::errorLevel)
)
/**
diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/SimpleManualPermissionEnforcementDetector.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/SimpleManualPermissionEnforcementDetector.kt
index 8ff813e4663e..9999a0ba7e06 100644
--- a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/SimpleManualPermissionEnforcementDetector.kt
+++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/SimpleManualPermissionEnforcementDetector.kt
@@ -18,6 +18,7 @@ package com.google.android.lint.aidl
import com.android.tools.lint.detector.api.Category
import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Incident
import com.android.tools.lint.detector.api.Issue
import com.android.tools.lint.detector.api.JavaContext
import com.android.tools.lint.detector.api.Scope
@@ -48,14 +49,22 @@ class SimpleManualPermissionEnforcementDetector : AidlImplementationDetector() {
val enforcePermissionFix = accumulateSimplePermissionCheckFixes(body, context) ?: return
val lintFix = enforcePermissionFix.toLintFix(context.getLocation(node))
val message =
- "$interfaceName permission check can be converted to @EnforcePermission annotation"
+ "$interfaceName permission check ${
+ if (enforcePermissionFix.errorLevel) "should" else "can"
+ } be converted to @EnforcePermission annotation"
- context.report(
+ val incident = Incident(
ISSUE_SIMPLE_MANUAL_PERMISSION_ENFORCEMENT,
enforcePermissionFix.locations.last(),
message,
lintFix
)
+
+ if (enforcePermissionFix.errorLevel) {
+ incident.overrideSeverity(Severity.ERROR)
+ }
+
+ context.report(incident)
}
/**
diff --git a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/SimpleManualPermissionEnforcementDetectorTest.kt b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/SimpleManualPermissionEnforcementDetectorTest.kt
index 50d5081c4b0d..bdf9c897b946 100644
--- a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/SimpleManualPermissionEnforcementDetectorTest.kt
+++ b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/SimpleManualPermissionEnforcementDetectorTest.kt
@@ -51,10 +51,10 @@ class SimpleManualPermissionEnforcementDetectorTest : LintDetectorTest() {
.run()
.expect(
"""
- src/Foo.java:7: Warning: ITest permission check can be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
+ src/Foo.java:7: Error: ITest permission check should be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
mContext.enforceCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo");
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- 0 errors, 1 warnings
+ 1 errors, 0 warnings
"""
)
.expectFixDiffs(
@@ -68,6 +68,80 @@ class SimpleManualPermissionEnforcementDetectorTest : LintDetectorTest() {
)
}
+ fun testClass_orSelfFalse_warning() {
+ lint().files(
+ java(
+ """
+ import android.content.Context;
+ import android.test.ITest;
+ public class Foo extends ITest.Stub {
+ private Context mContext;
+ @Override
+ public void test() throws android.os.RemoteException {
+ mContext.enforceCallingPermission("android.permission.READ_CONTACTS", "foo");
+ }
+ }
+ """
+ ).indented(),
+ *stubs
+ )
+ .run()
+ .expect(
+ """
+ src/Foo.java:7: Warning: ITest permission check can be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
+ mContext.enforceCallingPermission("android.permission.READ_CONTACTS", "foo");
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ 0 errors, 1 warnings
+ """
+ )
+ .expectFixDiffs(
+ """
+ Fix for src/Foo.java line 7: Annotate with @EnforcePermission:
+ @@ -5 +5
+ + @android.annotation.EnforcePermission("android.permission.READ_CONTACTS")
+ @@ -7 +8
+ - mContext.enforceCallingPermission("android.permission.READ_CONTACTS", "foo");
+ """
+ )
+ }
+
+ fun testClass_enforcesFalse_warning() {
+ lint().files(
+ java(
+ """
+ import android.content.Context;
+ import android.test.ITest;
+ public class Foo extends ITest.Stub {
+ private Context mContext;
+ @Override
+ public void test() throws android.os.RemoteException {
+ mContext.checkCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo");
+ }
+ }
+ """
+ ).indented(),
+ *stubs
+ )
+ .run()
+ .expect(
+ """
+ src/Foo.java:7: Warning: ITest permission check can be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
+ mContext.checkCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo");
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ 0 errors, 1 warnings
+ """
+ )
+ .expectFixDiffs(
+ """
+ Fix for src/Foo.java line 7: Annotate with @EnforcePermission:
+ @@ -5 +5
+ + @android.annotation.EnforcePermission("android.permission.READ_CONTACTS")
+ @@ -7 +8
+ - mContext.checkCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo");
+ """
+ )
+ }
+
fun testAnonClass() {
lint().files(
java(
@@ -91,10 +165,10 @@ class SimpleManualPermissionEnforcementDetectorTest : LintDetectorTest() {
.run()
.expect(
"""
- src/Foo.java:8: Warning: ITest permission check can be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
+ src/Foo.java:8: Error: ITest permission check should be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
mContext.enforceCallingOrSelfPermission(
^
- 0 errors, 1 warnings
+ 1 errors, 0 warnings
"""
)
.expectFixDiffs(
@@ -131,10 +205,10 @@ class SimpleManualPermissionEnforcementDetectorTest : LintDetectorTest() {
.run()
.expect(
"""
- src/Foo.java:8: Warning: ITest permission check can be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
+ src/Foo.java:8: Error: ITest permission check should be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.READ_CONTACTS, "foo");
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- 0 errors, 1 warnings
+ 1 errors, 0 warnings
"""
)
.expectFixDiffs(
@@ -173,10 +247,10 @@ class SimpleManualPermissionEnforcementDetectorTest : LintDetectorTest() {
.run()
.expect(
"""
- src/Foo.java:10: Warning: ITest permission check can be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
+ src/Foo.java:10: Error: ITest permission check should be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
mContext.enforceCallingOrSelfPermission(
^
- 0 errors, 1 warnings
+ 1 errors, 0 warnings
"""
)
.expectFixDiffs(
@@ -193,6 +267,96 @@ class SimpleManualPermissionEnforcementDetectorTest : LintDetectorTest() {
)
}
+ fun testAllOf_mixedOrSelf_warning() {
+ lint().files(
+ java(
+ """
+ import android.content.Context;
+ import android.test.ITest;
+ public class Foo {
+ private Context mContext;
+ private ITest itest = new ITest.Stub() {
+ @Override
+ public void test() throws android.os.RemoteException {
+ mContext.enforceCallingOrSelfPermission(
+ "android.permission.READ_CONTACTS", "foo");
+ mContext.enforceCallingPermission(
+ "android.permission.WRITE_CONTACTS", "foo");
+ }
+ };
+ }
+ """
+ ).indented(),
+ *stubs
+ )
+ .run()
+ .expect(
+ """
+ src/Foo.java:10: Warning: ITest permission check can be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
+ mContext.enforceCallingPermission(
+ ^
+ 0 errors, 1 warnings
+ """
+ )
+ .expectFixDiffs(
+ """
+ Fix for src/Foo.java line 10: Annotate with @EnforcePermission:
+ @@ -6 +6
+ + @android.annotation.EnforcePermission(allOf={"android.permission.READ_CONTACTS", "android.permission.WRITE_CONTACTS"})
+ @@ -8 +9
+ - mContext.enforceCallingOrSelfPermission(
+ - "android.permission.READ_CONTACTS", "foo");
+ - mContext.enforceCallingPermission(
+ - "android.permission.WRITE_CONTACTS", "foo");
+ """
+ )
+ }
+
+ fun testAllOf_mixedEnforces_warning() {
+ lint().files(
+ java(
+ """
+ import android.content.Context;
+ import android.test.ITest;
+ public class Foo {
+ private Context mContext;
+ private ITest itest = new ITest.Stub() {
+ @Override
+ public void test() throws android.os.RemoteException {
+ mContext.enforceCallingOrSelfPermission(
+ "android.permission.READ_CONTACTS", "foo");
+ mContext.checkCallingOrSelfPermission(
+ "android.permission.WRITE_CONTACTS", "foo");
+ }
+ };
+ }
+ """
+ ).indented(),
+ *stubs
+ )
+ .run()
+ .expect(
+ """
+ src/Foo.java:10: Warning: ITest permission check can be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
+ mContext.checkCallingOrSelfPermission(
+ ^
+ 0 errors, 1 warnings
+ """
+ )
+ .expectFixDiffs(
+ """
+ Fix for src/Foo.java line 10: Annotate with @EnforcePermission:
+ @@ -6 +6
+ + @android.annotation.EnforcePermission(allOf={"android.permission.READ_CONTACTS", "android.permission.WRITE_CONTACTS"})
+ @@ -8 +9
+ - mContext.enforceCallingOrSelfPermission(
+ - "android.permission.READ_CONTACTS", "foo");
+ - mContext.checkCallingOrSelfPermission(
+ - "android.permission.WRITE_CONTACTS", "foo");
+ """
+ )
+ }
+
fun testPrecedingExpressions() {
lint().files(
java(
@@ -225,7 +389,7 @@ class SimpleManualPermissionEnforcementDetectorTest : LintDetectorTest() {
public class Foo extends ITest.Stub {
private Context mContext;
- @android.content.pm.PermissionMethod
+ @android.content.pm.PermissionMethod(orSelf = true)
private void helper() {
mContext.enforceCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo");
}
@@ -242,10 +406,10 @@ class SimpleManualPermissionEnforcementDetectorTest : LintDetectorTest() {
.run()
.expect(
"""
- src/Foo.java:14: Warning: ITest permission check can be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
+ src/Foo.java:14: Error: ITest permission check should be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
helper();
~~~~~~~~~
- 0 errors, 1 warnings
+ 1 errors, 0 warnings
"""
)
.expectFixDiffs(
@@ -259,6 +423,50 @@ class SimpleManualPermissionEnforcementDetectorTest : LintDetectorTest() {
)
}
+ fun testPermissionHelper_orSelfNotBubbledUp_warning() {
+ lint().files(
+ java(
+ """
+ import android.content.Context;
+ import android.test.ITest;
+
+ public class Foo extends ITest.Stub {
+ private Context mContext;
+
+ @android.content.pm.PermissionMethod
+ private void helper() {
+ mContext.enforceCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo");
+ }
+
+ @Override
+ public void test() throws android.os.RemoteException {
+ helper();
+ }
+ }
+ """
+ ).indented(),
+ *stubs
+ )
+ .run()
+ .expect(
+ """
+ src/Foo.java:14: Warning: ITest permission check can be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
+ helper();
+ ~~~~~~~~~
+ 0 errors, 1 warnings
+ """
+ )
+ .expectFixDiffs(
+ """
+ Fix for src/Foo.java line 14: Annotate with @EnforcePermission:
+ @@ -12 +12
+ + @android.annotation.EnforcePermission("android.permission.READ_CONTACTS")
+ @@ -14 +15
+ - helper();
+ """
+ )
+ }
+
fun testPermissionHelperAllOf() {
lint().files(
java(
@@ -269,7 +477,7 @@ class SimpleManualPermissionEnforcementDetectorTest : LintDetectorTest() {
public class Foo extends ITest.Stub {
private Context mContext;
- @android.content.pm.PermissionMethod
+ @android.content.pm.PermissionMethod(orSelf = true)
private void helper() {
mContext.enforceCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo");
mContext.enforceCallingOrSelfPermission("android.permission.WRITE_CONTACTS", "foo");
@@ -288,10 +496,10 @@ class SimpleManualPermissionEnforcementDetectorTest : LintDetectorTest() {
.run()
.expect(
"""
- src/Foo.java:16: Warning: ITest permission check can be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
+ src/Foo.java:16: Error: ITest permission check should be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
mContext.enforceCallingOrSelfPermission("FOO", "foo");
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- 0 errors, 1 warnings
+ 1 errors, 0 warnings
"""
)
.expectFixDiffs(
@@ -317,12 +525,12 @@ class SimpleManualPermissionEnforcementDetectorTest : LintDetectorTest() {
public class Foo extends ITest.Stub {
private Context mContext;
- @android.content.pm.PermissionMethod
+ @android.content.pm.PermissionMethod(orSelf = true)
private void helperHelper() {
helper("android.permission.WRITE_CONTACTS");
}
- @android.content.pm.PermissionMethod
+ @android.content.pm.PermissionMethod(orSelf = true)
private void helper(@android.content.pm.PermissionName String extraPermission) {
mContext.enforceCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo");
}
@@ -339,10 +547,10 @@ class SimpleManualPermissionEnforcementDetectorTest : LintDetectorTest() {
.run()
.expect(
"""
- src/Foo.java:19: Warning: ITest permission check can be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
+ src/Foo.java:19: Error: ITest permission check should be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
helperHelper();
~~~~~~~~~~~~~~~
- 0 errors, 1 warnings
+ 1 errors, 0 warnings
"""
)
.expectFixDiffs(
diff --git a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/Stubs.kt b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/Stubs.kt
index bd6b1952847c..5ac8a0ba2604 100644
--- a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/Stubs.kt
+++ b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/Stubs.kt
@@ -17,8 +17,12 @@ val contextStub: TestFile = java(
"""
package android.content;
public class Context {
- @android.content.pm.PermissionMethod
+ @android.content.pm.PermissionMethod(orSelf = true)
public void enforceCallingOrSelfPermission(@android.content.pm.PermissionName String permission, String message) {}
+ @android.content.pm.PermissionMethod
+ public void enforceCallingPermission(@android.content.pm.PermissionName String permission, String message) {}
+ @android.content.pm.PermissionMethod(orSelf = true)
+ public int checkCallingOrSelfPermission(@android.content.pm.PermissionName String permission, String message) {}
}
"""
).indented()
diff --git a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
index d85a5bdc3e66..5da18dc8ccad 100644
--- a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
+++ b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
@@ -393,6 +393,21 @@ public class WifiNl80211Manager {
mEventHandler = new Handler(context.getMainLooper());
}
+ /**
+ * Construct WifiNl80211Manager with giving context and binder which is an interface of
+ * IWificond.
+ *
+ * @param context Android context.
+ * @param binder a binder of IWificond.
+ */
+ public WifiNl80211Manager(@NonNull Context context, @NonNull IBinder binder) {
+ this(context);
+ mWificond = IWificond.Stub.asInterface(binder);
+ if (mWificond == null) {
+ Log.e(TAG, "Failed to get reference to wificond");
+ }
+ }
+
/** @hide */
@VisibleForTesting
public WifiNl80211Manager(Context context, IWificond wificond) {